← 返回博客

敲门声检测:嵌入式离线敲门识别 SDK 与看护求助新增

敲门声检测的价值(场景、流程)

不是每个访客都会按门铃:快递员敲两下就走、邻居顺手敲窗、小朋友够不到按钮。纯按钮触发的门铃会漏掉这些访客;运动检测又分不清"有人路过"和"有人敲门"。敲门检测补齐这个缺口——而且同一套引擎换个配置,就是养老看护里的求助通道。

门口场景与事件链
敲门 → 识别 → 唤醒 → 推送,端到端
访客/设备/用户三方业务流
敲门事件业务流(泳道图)

敲门声的特点:与脚步、关门的区分

敲门声特征对比
脉冲组 vs 低频周期脚步 vs 慢衰减关门

下图为一条真实敲门声音频——上为波形图、下为梅尔频谱图,独立的短促脉冲在干净的背景上非常突出:

真实敲门声:波形与频谱
真实敲门声示例:波形(上)+ 梅尔频谱(下)

试听素材(真实样本,点击播放):

🔊 真实敲门声样本

🔊 另一段敲门声样本

敲门 — ——2–4 次脉冲成组,间隔符合人手节奏;脉冲成组是核心证据

脚步 — ——低频、宽包络、连续重复;没有尖锐冲击

关门 — ——低频大能量的单次爆发,衰减更慢;频谱重心明显更低

事件化检测对风噪天然不敏感:持续声没有脉冲结构。

识别方法:预处理 → 特征提取 → 算法

端到端流程
采集、检测、干扰过滤、唤醒与推送

管线:采集 → 敲击事件检测(瞬态 + 特征判定,干扰过滤分支拦截脚步/关门/风噪)→ 视频唤醒 → 通知推送。响应速度是关键:敲门落下的瞬间唤醒视频门铃。

端侧系统结构
采集、处理、引擎、输出、应用

四类应用形态

四类应用形态
门铃、门锁、无人店、养老看护

智能门铃 — ——敲门瞬间唤醒视频画面,推送"有人敲门"

智能门锁 — ——零额外硬件的访客感知

无人值守门店 — ——闭店时段敲击告警 + 远程复核

养老看护 — ——敲击即求助(见下节)

养老看护:敲击即求助

对行动不便的老人,手机可能够不着、急救按钮容易忘记——但床护栏就在手边。敲击床栏或桌面,是最自然、最省力的求助表达。

老人房夜间场景
床护栏敲击、床头设备与子女端通知
分级响应流程
单次轻敲 vs 连续急敲与升级规则

单次轻敲 — ——仅推送提醒(可能是翻身调整)

连续 3 次以上 / 大强度模式 — ——紧急级:电话外呼 + 强提醒(含音频)

夜间时段(22:00–07:00) — ——阈值更敏感

2 分钟无响应 — ——升级下一位联系人(邻居、亲属、照护者)

四个设计考量
灵敏度分层、干扰处理、低功耗与隐私

日夜间灵敏度分层;用脉冲节奏区分"有意敲击"与"无意碰撞";无长供电的床头设备需要低功耗长待机;隐私友好——音频不出设备,纯声学方案无需摄像头。养老类产品的共同原则:宁可多提醒几次,也不能漏掉求助。

时段与响应数据
时段分布(夜间占比 58%)与响应时效(示意)

敲击事件集中在夜间——恰好是响应最困难的时段。响应链目标:5 秒内通知、1 分钟内对讲、2 分钟内升级。

定位说明:这是加快人工响应的照护辅助工具——真正的紧急情况请直接拨打急救电话。

准确率与性能

检出率数据
门型 × 距离检出率(示意)

0.5–2 米范围内检出率保持 94% 以上——门体越厚重、叩击传导衰减越明显,近场拾音位置因此关键。部署要点:麦克风靠近门内表面;避开电机与风扇;安装时用实际门体试敲标定。

项目
指标
准确率
95.0%
误报率
<2%
模型大小
0.2–1MB(INT8)
推理延迟
灵活可调(视平台资源)
采样率
16kHz
支持平台
ARM Linux / MIPS / x86_64;SVP / Magik 预适配

注:文中性能指标基于内部测试环境,实际表现取决于硬件配置与使用场景。

运行平台与硬件要求

门铃与门锁级 SoC;CPU 占用低于 50MHz;与视频、网络业务共享 SoC;门锁与看护设备可用低功耗事件架构支撑电池续航。

C 接口与嵌入式集成

「敲击声」识别库提供简洁的流式 C 接口:调用方只需持续送入 16kHz 单声道 PCM,拼帧、Mel 预处理与模型推理全部在内部完成,帧级概率经报警策略聚合成「敲击事件」回调输出。

完整接口声明(knock_detect.h):

knock_detect.hc
/**
 * knock_detect.h — 敲击声识别统一接口
 *
 * 封装 Mel 预处理 + 推理引擎 + 报警策略, 内部模型消费线程处理音频。
 * 与录音模块 (audio_capture.h) 相互独立: 调用者自行决定音频来源
 * (录音回调 / wav 文件 / 网络流), 通过 knock_detect_feed 送入, 数据任意大小。
 *
 * 用法 (实时录音模式):
 *   knock_detect_t *d = knock_detect_create(mgk_path, NULL, NULL);
 *   knock_detect_set_listener(d, on_frame, on_onset, on_offset, NULL);
 *   knock_detect_start(d);                          // 启动内部模型消费线程
 *   audio_capture_start(rec, capture_cb, d);        // 录音回调里调 knock_detect_feed
 *   ...
 *   knock_detect_stop(d);                           // 排空缓冲, 停止线程
 *   knock_detect_destroy(d);
 *
 * 用法 (wav 文件模式):
 *   knock_detect_t *d = knock_detect_create(mgk_path, NULL, NULL);
 *   knock_detect_set_listener(d, on_frame, on_onset, on_offset, NULL);
 *   knock_detect_start(d);
 *   循环读文件: knock_detect_feed(d, pcm, n);       // 任意数据大小
 *   knock_detect_stop(d);
 *   knock_detect_destroy(d);
 */

#ifndef KNOCK_DETECT_H
#define KNOCK_DETECT_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/* 识别事件 (报警策略输出, 用于事件结束回调) */
typedef struct {
    float start_time;       /* 事件开始时间 (秒) */
    float end_time;         /* 事件结束时间 (秒) */
    float confidence;       /* 事件置信度 */
    float max_confidence;   /* 事件内最大帧置信度 */
    int   frame_count;      /* 事件持续帧数 */
} knock_detect_event_t;

/* 帧级回调: 每帧识别结果 (模型线程内执行) */
typedef void (*knock_detect_frame_cb_t)(float knock_prob, float timestamp,
                                        void *user_data);

/* 事件开始回调: 策略判定敲击事件开始, 只有开始时间 */
typedef void (*knock_detect_onset_cb_t)(float start_time, void *user_data);

/* 事件结束回调: 策略判定敲击事件结束 (或停止识别时未结束的事件), 完整事件信息 */
typedef void (*knock_detect_offset_cb_t)(const knock_detect_event_t *event,
                                         void *user_data);

typedef struct knock_detect_s knock_detect_t;

/* 创建/销毁; alarm_name/alarm_params 可传 NULL (用默认策略及参数) */
knock_detect_t *knock_detect_create(const char *mgk_path,      /* 模型文件路径 (必填) */
                                    const char *alarm_name,    /* 报警策略名, NULL=默认 */
                                    const char *alarm_params); /* 策略参数 key=val,key=val, NULL=默认 */
void knock_detect_destroy(knock_detect_t *det);

/* 设置事件回调 (create 后调用, 也可在运行中调整); 不需要的回调传 NULL */
void knock_detect_set_listener(knock_detect_t *det,
                               knock_detect_frame_cb_t  on_frame,
                               knock_detect_onset_cb_t  on_onset,
                               knock_detect_offset_cb_t on_offset,
                               void *user_data);

/* 启动/停止识别: 启动内部模型消费线程 / 排空缓冲后停止线程 */
int  knock_detect_start(knock_detect_t *det);
void knock_detect_stop(knock_detect_t *det);
int  knock_detect_is_running(knock_detect_t *det);

/* 设置事件识别策略 (可在运行中调整) */
int knock_detect_set_alarm(knock_detect_t *det, const char *alarm_name,
                           const char *alarm_params);

/* 送入 PCM 数据 (16bit 单声道 16kHz), 线程安全, 任意数据大小 */
int knock_detect_feed(knock_detect_t *det, const int16_t *pcm, int num_samples);

#ifdef __cplusplus
}
#endif

#endif /* KNOCK_DETECT_H */

最简 wav 推理示例(节选,完整文件位于 SDK 的 src/knockDetect/c/knock_demo.c):

knock_demo.cc
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "knock_detect.h"

#define DEFAULT_MGK  "knock_detect_v7.mgk"  /* 模型文件 */
#define DEFAULT_WAV  "test_knock.wav"       /* 16kHz 单声道 16bit PCM */
#define FEED_CHUNK   16000                  /* 每次送入 1 秒音频 */

/* 帧级回调: 每帧输出敲击声概率 (约 1 秒一帧) */
static void on_frame(float knock_prob, float timestamp, void *user_data)
{
    (void)user_data;
    printf("%8.2fs  knock=%.4f\n", timestamp, knock_prob);
}

/* 事件开始回调: 策略判定敲击事件开始 */
static void on_onset(float start_time, void *user_data)
{
    (void)user_data;
    printf("[EVENT] knock start at %.2fs\n", start_time);
}

/* 事件结束回调: 应用层可据此做后续统计或分级响应 */
static void on_offset(const knock_detect_event_t *ev, void *user_data)
{
    (void)user_data;
    printf("[EVENT] knock end at %.2fs (dur=%.2fs, conf=%.3f, frames=%d)\n",
           ev->end_time, ev->end_time - ev->start_time,
           ev->confidence, ev->frame_count);
}

static int read_wav_pcm(const char *path, int16_t **pcm, int *n, int *sr);  /* 完整实现见源文件 */

int main(void)
{
    knock_detect_t *det;
    int16_t *pcm = NULL;
    int num_samples = 0, sample_rate = 0;
    int pos;

    if (read_wav_pcm(DEFAULT_WAV, &pcm, &num_samples, &sample_rate) != 0)
        return 1;

    /* 1. 创建识别器: 模型文件 + 默认报警策略 (NULL) */
    det = knock_detect_create(DEFAULT_MGK, NULL, NULL);
    if (!det) return 1;

    /* 2. 注册回调 (均为可选) */
    knock_detect_set_listener(det, on_frame, on_onset, on_offset, NULL);

    /* 3. 启动内部模型消费线程 */
    knock_detect_start(det);

    /* 4. 分块送入 PCM; 实时录音时改在录音回调里 feed */
    for (pos = 0; pos < num_samples; pos += FEED_CHUNK) {
        int n = num_samples - pos;
        if (n > FEED_CHUNK) n = FEED_CHUNK;
        knock_detect_feed(det, pcm + pos, n);
    }

    /* 5. 停止并销毁 */
    knock_detect_stop(det);
    knock_detect_destroy(det);
    free(pcm);
    return 0;
}

编译与运行(示例):

buildbash
$(CC) knock_demo.c -I. -L. -lknockdetect -lpthread -lm -o knock_demo
./knock_demo

结语

敲击,是设备能"听懂"的最简单的一种表达——从门口的访客感知,到床边的求助通道。我们提供在线试用与照护场景支持。