婴儿监视器应用开发:从音频检测到内存管理
在开发一个婴儿监视器应用时,我们需要实现三个关键类:
AppAudio
、
AppRmaker
和
AppMem
。下面将详细介绍这些类的实现过程。
1. AppAudio 类的实现
AppAudio
类主要负责婴儿哭声的检测。由于音频处理和机器学习推理需要大量内存,而 ESP32 的内部 RAM 无法满足需求,因此我们需要将一些缓冲区和任务栈移动到 ESP32 - S3 模块的 SPIRAM 中。
以下是
main/AppAudio.hpp
头文件中
AppAudio
类的实现:
#pragma once
#include <cstring>
#include <mutex>
#include <functional>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
#include "dl_lib_coefgetter_if.h"
#include "esp_afe_sr_models.h"
#include "esp_board_init.h"
#include "model_path.h"
namespace app
{
class AppAudio
{
private:
static constexpr int CRYING_IDX{0};
static constexpr int NOISE_IDX{1};
static constexpr int AUDIO_BUFFER_SIZE{16000};
float *m_audio_buffer;
float *m_features;
int m_audio_buffer_ptr{0};
std::mutex m_features_mutex;
static constexpr int DETECT_TASK_BUFF_SIZE{4 * 1024};
inline static uint8_t *m_detect_task_buffer;
inline static StaticTask_t m_detect_task_data;
static constexpr int ACTION_TASK_BUFF_SIZE{4 * 1024};
inline static uint8_t *m_action_task_buffer;
inline static StaticTask_t m_action_task_data;
static constexpr int FEED_TASK_BUFF_SIZE{8 * 1024};
inline static uint8_t m_feed_task_buffer[FEED_TASK_BUFF_SIZE];
inline static StaticTask_t m_feed_task_data;
esp_afe_sr_iface_t *m_afe_handle{nullptr};
esp_afe_sr_data_t *m_afe_data{nullptr};
std::function<void(bool)> m_crying_fn;
bool m_crying;
static void feedTask(void *arg);
static void detectTask(void *arg);
static void actionTask(void *arg);
static afe_config_t defaultAfeConfig();
public:
void init(std::function<void(bool)> f)
{
m_crying_fn = f;
m_crying = false;
m_audio_buffer = new float[AUDIO_BUFFER_SIZE];
m_features = new float[AUDIO_BUFFER_SIZE];
m_detect_task_buffer = new uint8_t[DETECT_TASK_BUFF_SIZE];
m_action_task_buffer = new uint8_t[ACTION_TASK_BUFF_SIZE];
esp_board_init(AUDIO_HAL_16K_SAMPLES, 1, 16);
m_afe_handle = const_cast<esp_afe_sr_iface_t *>(&ESP_AFE_VC_HANDLE);
afe_config_t afe_config = defaultAfeConfig();
m_afe_data = m_afe_handle->create_from_config(&afe_config);
}
void start(void)
{
xTaskCreateStaticPinnedToCore(feedTask, "feed",
FEED_TASK_BUFF_SIZE, this, 5, m_feed_task_buffer,
&m_feed_task_data, 0);
xTaskCreateStaticPinnedToCore(detectTask, "detect",
DETECT_TASK_BUFF_SIZE, this, 5,
m_detect_task_buffer, &m_detect_task_data, 1);
xTaskCreateStaticPinnedToCore(actionTask, "action",
ACTION_TASK_BUFF_SIZE, this, 5,
m_action_task_buffer, &m_action_task_data, 1);
}
};
void AppAudio::feedTask(void *arg)
{
AppAudio *obj{static_cast<AppAudio *>(arg)};
int audio_chunksize = obj->m_afe_handle->get_feed_chunksize(obj->m_afe_data);
int feed_channel = esp_get_feed_channel();
int16_t *i2s_buff = new int16_t[audio_chunksize * feed_channel];
while (true)
{
esp_get_feed_data(false, i2s_buff, audio_chunksize * sizeof(int16_t) * feed_channel);
obj->m_afe_handle->feed(obj->m_afe_data, i2s_buff);
}
}
void AppAudio::detectTask(void *arg)
{
AppAudio *obj{static_cast<AppAudio *>(arg)};
int afe_chunksize{obj->m_afe_handle->get_fetch_chunksize(obj->m_afe_data)};
while (true)
{
afe_fetch_result_t *res = obj->m_afe_handle->fetch(obj->m_afe_data);
if (res == nullptr || res->ret_value == ESP_FAIL)
{
continue;
}
for (int i = 0; i < afe_chunksize; ++i)
{
obj->m_audio_buffer_ptr %= AUDIO_BUFFER_SIZE;
obj->m_audio_buffer[obj->m_audio_buffer_ptr++] = res->data[i];
}
{
std::lock_guard<std::mutex> guard(obj->m_features_mutex);
for (int i = 0; i < AUDIO_BUFFER_SIZE; ++i)
{
obj->m_features[i] = obj->m_audio_buffer[(obj->m_audio_buffer_ptr + i) % AUDIO_BUFFER_SIZE];
}
}
}
}
void AppAudio::actionTask(void *arg)
{
AppAudio *obj{static_cast<AppAudio *>(arg)};
ei_impulse_result_t result = {nullptr};
auto get_data_fn = [&obj](size_t offset, size_t length, float *out_ptr) -> int
{
memcpy(out_ptr, obj->m_features + offset, length * sizeof(float));
return 0;
};
while (true)
{
signal_t features_signal{get_data_fn, AUDIO_BUFFER_SIZE};
int result_idx{NOISE_IDX};
{
std::lock_guard<std::mutex> guard(obj->m_features_mutex);
if (run_classifier(&features_signal, &result) == EI_IMPULSE_OK)
{
for (uint16_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; ++i)
{
if (result.classification[i].value > result.classification[result_idx].value)
{
result_idx = i;
}
}
}
}
switch (result_idx)
{
case CRYING_IDX:
{
if (!obj->m_crying)
{
obj->m_crying_fn(true);
obj->m_crying = true;
}
}
break;
case NOISE_IDX:
{
if (obj->m_crying)
{
obj->m_crying_fn(false);
obj->m_crying = false;
}
}
break;
default:
break;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
afe_config_t AppAudio::defaultAfeConfig()
{
return {
.aec_init = false,
.se_init = true,
.vad_init = false,
.wakenet_init = false,
.voice_communication_init = true,
.voice_communication_agc_init = false,
.voice_communication_agc_gain = 15,
.vad_mode = VAD_MODE_3,
.wakenet_model_name = nullptr,
.wakenet_mode = DET_MODE_2CH_90,
.afe_mode = SR_MODE_LOW_COST,
.afe_perferred_core = 0,
.afe_perferred_priority = 5,
.afe_ringbuf_size = 50,
.memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM,
.agc_mode = AFE_MN_PEAK_AGC_MODE_2,
.pcm_config = {3, 2, 1, 16000},
.debug_init = false,
};
}
}
以下是
AppAudio
类的主要功能和实现步骤:
-
成员变量定义
:
-
CRYING_IDX
和
NOISE_IDX
:表示推理结果的索引。
-
m_audio_buffer
和
m_features
:音频处理缓冲区,需要分配到外部 RAM。
- 多个任务缓冲区和任务数据结构,用于 FreeRTOS 任务。
-
m_afe_handle
和
m_afe_data
:AFE 相关句柄。
-
m_crying_fn
:新哭声事件的回调函数。
-
m_crying
:表示最后一次 ML 推理结果是否为哭声事件。
-
init 函数
:
- 保存回调函数
m_crying_fn
。
- 分配
m_audio_buffer
和
m_features
到外部 RAM。
- 分配检测和动作任务的栈到外部 RAM。
- 初始化开发板和 AFE。
-
start 函数
:
- 创建三个 FreeRTOS 任务:
feedTask
、
detectTask
和
actionTask
。
-
feedTask 函数
:
- 从音频子系统获取原始音频数据。
- 将数据传递给 AFE 进行处理。
-
detectTask 函数
:
- 从 AFE 获取预处理后的单通道数据。
- 将数据复制到
m_audio_buffer
中。
- 将
m_audio_buffer
中的数据复制到
m_features
中。
-
actionTask 函数
:
- 运行 ML 推理。
- 根据推理结果调用回调函数。
-
defaultAfeConfig 函数
:
- 返回 AFE 的配置,使用更多的 PSRAM 以节省内部 RAM。
2. AppRmaker 类的实现
AppRmaker
类用于与 ESP RainMaker 平台集成,实现婴儿哭声状态的上报和通知功能。
以下是
main/AppRmaker.hpp
头文件中
AppRmaker
类的实现:
#pragma once
#include <cstring>
#include <cstdint>
#include <functional>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <esp_rmaker_core.h>
#include <esp_rmaker_standard_types.h>
#include <esp_rmaker_standard_params.h>
#include <esp_rmaker_standard_devices.h>
#include <esp_rmaker_common_events.h>
#include <esp_rmaker_utils.h>
#include <esp_rmaker_mqtt.h>
namespace app
{
class AppRmaker
{
private:
esp_rmaker_node_t *m_rmaker_node;
esp_rmaker_device_t *m_device;
esp_rmaker_param_t *m_cry_param;
public:
void init()
{
esp_rmaker_time_set_timezone(CONFIG_ESP_RMAKER_DEF_TIMEZONE);
esp_rmaker_config_t rainmaker_cfg = {
.enable_time_sync = true,
};
m_rmaker_node = esp_rmaker_node_init(&rainmaker_cfg, "Baby node", "esp.node.sensor");
m_device = esp_rmaker_device_create("Cry sensor", "esp.device.sensor", (void *)this);
esp_rmaker_device_add_param(m_device, esp_rmaker_name_param_create(ESP_RMAKER_DEF_NAME_PARAM, "Cry sensor"));
m_cry_param = esp_rmaker_param_create("Baby crying", "esp.param.toggle", esp_rmaker_bool(false), PROP_FLAG_READ);
esp_rmaker_param_add_ui_type(m_cry_param, ESP_RMAKER_UI_TOGGLE);
esp_rmaker_device_add_param(m_device, m_cry_param);
esp_rmaker_device_assign_primary_param(m_device, m_cry_param);
esp_rmaker_node_add_device(m_rmaker_node, m_device);
}
void start()
{
esp_rmaker_start();
}
void update(bool state)
{
esp_rmaker_param_update_and_report(m_cry_param, esp_rmaker_bool(state));
if (state)
{
esp_rmaker_raise_alert("crying");
}
}
};
}
以下是
AppRmaker
类的主要功能和实现步骤:
-
成员变量定义
:
-
m_rmaker_node
:RainMaker 节点。
-
m_device
:RainMaker 设备。
-
m_cry_param
:婴儿哭声状态参数。
-
init 函数
:
- 设置时区并启用时间同步。
- 创建 RainMaker 节点。
- 创建 RainMaker 设备并添加名称参数。
- 创建婴儿哭声状态参数并添加到设备中。
- 将设备添加到节点中。
-
start 函数
:
- 启动 RainMaker 代理。
-
update 函数
:
- 更新婴儿哭声状态参数。
- 如果状态为哭声,则触发警报。
3. AppMem 类的实现
AppMem
类用于监控内部和外部堆的使用情况,帮助我们了解应用程序在运行时的内存使用情况。
以下是
main/AppMem.hpp
头文件中
AppMem
类的实现:
#pragma once
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
namespace app
{
class AppMem
{
private:
constexpr static const char *TAG{"app-mem"};
esp_timer_handle_t m_periodic_timer;
static void periodic_timer_callback(void *arg)
{
ESP_LOGI(TAG, "------- mem stats -------");
ESP_LOGI(TAG, "internal\t: %10u (free) / %10u(total)",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL), heap_caps_get_total_size(MALLOC_CAP_INTERNAL));
ESP_LOGI(TAG, "spiram\t: %10u (free) / %10u (total)",
heap_caps_get_free_size(MALLOC_CAP_SPIRAM), heap_caps_get_total_size(MALLOC_CAP_SPIRAM));
}
public:
void monitor(void)
{
const esp_timer_create_args_t periodic_timer_args = {
.callback = periodic_timer_callback,
.arg = this};
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &m_periodic_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(m_periodic_timer, 5000000u));
}
void print(void)
{
periodic_timer_callback(nullptr);
}
};
}
以下是
AppMem
类的主要功能和实现步骤:
-
成员变量定义
:
-
TAG
:日志标签。
-
m_periodic_timer
:周期性定时器。
-
periodic_timer_callback 函数
:
- 打印内部和外部堆的使用情况。
-
monitor 函数
:
- 创建周期性定时器。
- 启动定时器,每 5 秒执行一次回调函数。
-
print 函数
:
- 手动打印堆的使用情况。
4. 应用程序的整合
最后,我们需要在
main/main.cpp
源文件中整合这些类:
#include "AppAudio.hpp"
#include "AppDriver.hpp"
#include "AppRmaker.hpp"
#include "AppMem.hpp"
namespace
{
app::AppAudio app_audio;
app::AppDriver app_driver;
app::AppRmaker app_rmaker;
app::AppMem app_mem;
}
总结
通过实现
AppAudio
、
AppRmaker
和
AppMem
类,我们完成了一个婴儿监视器应用的开发。
AppAudio
类负责音频处理和哭声检测,
AppRmaker
类负责与 ESP RainMaker 平台集成,
AppMem
类负责监控内存使用情况。这些类的协同工作使得我们能够实时检测婴儿的哭声,并通过 RainMaker 平台进行状态上报和通知。
以下是整个应用程序的流程图:
graph TD;
A[开始] --> B[初始化 AppAudio];
B --> C[初始化 AppRmaker];
C --> D[初始化 AppMem];
D --> E[启动 AppAudio 任务];
E --> F[启动 AppRmaker 代理];
F --> G[启动 AppMem 监控];
G --> H[运行应用程序];
H --> I{检测到哭声?};
I -- 是 --> J[更新 AppRmaker 状态并触发警报];
I -- 否 --> H;
J --> H;
表格:类功能总结
| 类名 | 功能 |
|---|---|
| AppAudio | 音频处理和哭声检测 |
| AppRmaker | 与 ESP RainMaker 平台集成,状态上报和通知 |
| AppMem | 监控内部和外部堆的使用情况 |
通过以上步骤,我们可以开发出一个功能完善的婴儿监视器应用,实现音频检测、云平台集成和内存监控等功能。
婴儿监视器应用开发:从音频检测到内存管理
5. 详细功能分析
5.1 AppAudio 类功能深入剖析
-
音频数据处理流程
-
首先,
feedTask函数从音频子系统获取原始音频数据,这是整个音频处理的起点。它通过esp_get_feed_data函数获取数据,并将其传递给 AFE(音频前端)进行处理。AFE 会对数据进行滤波和通道合并等操作,将其转换为归一化的单通道数据。 -
接着,
detectTask函数从 AFE 中获取预处理后的单通道数据。它将这些数据存储在m_audio_buffer这个循环缓冲区中,当缓冲区满时会自动回绕。然后,将m_audio_buffer中的数据复制到m_features中,为后续的 ML 推理做准备。 -
最后,
actionTask函数使用m_features中的数据进行 ML 推理。它调用run_classifier函数进行推理,并根据推理结果调用回调函数m_crying_fn,通知外部有新的哭声事件发生。
-
首先,
-
内存管理策略
-
在
AppAudio类中,为了节省内部 RAM,将m_audio_buffer和m_features分配到外部 RAM。通过在sdkconfig中配置相关选项,使得malloc函数在分配超过 1KB 的内存时从外部 RAM 分配。同时,检测和动作任务的栈也分配到外部 RAM,而feedTask的栈则分配在内部 RAM,这是因为 AFE 函数在获取音频数据时使用外部内存作为任务栈会导致崩溃。
-
在
5.2 AppRmaker 类功能深入剖析
-
RainMaker 集成流程
-
init函数是集成的关键步骤。它首先设置时区并启用时间同步,然后创建 RainMaker 节点和设备。接着,创建婴儿哭声状态参数,并将其添加到设备中,最后将设备添加到节点中。 -
start函数启动 RainMaker 代理,使得设备能够与 RainMaker 平台进行通信。 -
update函数用于更新婴儿哭声状态参数,并在检测到哭声时触发警报,通过esp_rmaker_raise_alert函数通知用户。
-
-
用户交互设计
- 婴儿哭声参数在 RainMaker 移动应用的 GUI 上显示为一个只读的布尔值,以切换开关的形式呈现,方便用户直观地查看婴儿的状态。
5.3 AppMem 类功能深入剖析
-
内存监控机制
-
monitor函数创建一个周期性定时器,每 5 秒执行一次periodic_timer_callback函数。该回调函数会打印内部和外部堆的使用情况,让开发者实时了解应用程序的内存使用状态。 -
print函数允许开发者手动打印堆的使用情况,方便在特定时刻进行内存检查。
-
6. 代码优化建议
6.1 AppAudio 类优化
- 缓冲区管理优化 :可以考虑使用更高效的缓冲区管理算法,减少内存碎片。例如,采用环形缓冲区的动态调整策略,根据实际音频数据的流量调整缓冲区大小。
- 任务调度优化 :可以根据音频处理的实时性要求,调整 FreeRTOS 任务的优先级和调度策略,确保音频数据的及时处理。
6.2 AppRmaker 类优化
- 网络通信优化 :可以采用更高效的网络协议或优化 MQTT 通信参数,减少通信延迟和带宽消耗。
- 错误处理优化 :增加对 RainMaker 平台通信错误的处理机制,提高系统的稳定性。
6.3 AppMem 类优化
- 监控频率调整 :根据应用程序的实际运行情况,动态调整内存监控的频率,避免不必要的资源消耗。
7. 实际应用场景分析
7.1 家庭使用场景
- 在家庭环境中,该婴儿监视器应用可以实时监测婴儿的哭声。当婴儿哭泣时,通过 RainMaker 平台的通知功能,家长可以及时收到警报,了解婴儿的状态。同时,内存监控功能可以确保应用程序在长时间运行时的稳定性,避免因内存泄漏等问题导致系统崩溃。
7.2 公共场所使用场景
- 在托儿所、幼儿园等公共场所,多个婴儿监视器可以同时使用。通过 RainMaker 平台的集中管理功能,工作人员可以实时监控所有婴儿的状态,提高管理效率。
总结与展望
8. 总结
通过实现
AppAudio
、
AppRmaker
和
AppMem
类,我们成功开发了一个功能完善的婴儿监视器应用。该应用实现了音频处理和哭声检测、与 ESP RainMaker 平台的集成以及内存使用情况的监控。各个类之间协同工作,确保了应用程序的稳定性和可靠性。
9. 展望
- 功能扩展 :可以添加更多的音频特征分析功能,如哭声的强度、频率等,为家长提供更详细的信息。
- 智能化升级 :结合人工智能技术,实现对婴儿哭声的分类,例如区分饥饿、困倦、不适等不同原因的哭声。
- 用户体验优化 :进一步优化 RainMaker 平台的用户界面,提供更友好的交互体验。
表格:优化建议总结
| 类名 | 优化方向 | 具体建议 |
|---|---|---|
| AppAudio | 缓冲区管理 | 使用更高效的缓冲区管理算法,动态调整缓冲区大小 |
| AppAudio | 任务调度 | 根据实时性要求调整任务优先级和调度策略 |
| AppRmaker | 网络通信 | 采用更高效的网络协议,优化 MQTT 通信参数 |
| AppRmaker | 错误处理 | 增加对 RainMaker 平台通信错误的处理机制 |
| AppMem | 监控频率 | 动态调整内存监控的频率 |
流程图:应用程序优化流程
graph TD;
A[开始] --> B[分析应用程序性能];
B --> C{是否需要优化?};
C -- 是 --> D[选择优化方向];
D --> E[实施优化措施];
E --> F[测试优化效果];
F --> G{优化是否成功?};
G -- 是 --> H[结束];
G -- 否 --> D;
C -- 否 --> H;
通过以上的分析和优化建议,我们可以进一步完善婴儿监视器应用,使其在实际应用中发挥更大的作用。
超级会员免费看
766

被折叠的 条评论
为什么被折叠?



