snoreman 源码解析 — 用手机录鼾声并分析的 React Native App

> 一句话版本:一个 React Native 手机 App,睡觉时开着录鼾声,每秒采样一次分贝值,超过阈值就标记为"打鼾",第二天看波形图、统计数据和严重程度评估。

项目信息
来源https://github.com/timqian/snoreman
作者timqian(中国开发者)
创建时间2026-01-20
语言TypeScript / React Native (Expo)
Stars69Forks 19
许可证MIT

核心内容

项目结构


snoreman/
├── hooks/use-recording.ts       ← 核心:录音 + 分贝采样 + 打鼾检测
├── utils/storage.ts             ← 数据持久化 + 打鼾分析算法
├── components/
│   ├── audio-player-chart.tsx   ← 播放器 + 波形图 + 阈值滑块
│   ├── decibel-chart.tsx        ← 实时分贝图表 + 圆形仪表盘
│   └── recording-tips-modal.tsx ← 录音提示弹窗
├── app/
│   ├── (tabs)/index.tsx         ← 主页:录音列表 + 实时录音
│   ├── recording/[id].tsx       ← 详情页:波形回放 + 统计
│   ├── settings.tsx             ← 设置页:阈值、提醒、语言
│   └── _layout.tsx              ← 根布局 + 通知配置
├── locales/                     ← 中英文国际化
└── package.json                 ← Expo SDK 54, React 19

技术栈

技术用途
Expo SDK 54跨平台框架(iOS/Android/Web)
expo-audio录音(核心依赖)
expo-notifications睡前提醒
expo-file-system音频文件持久化
expo-screen-orientation横竖屏切换
AsyncStorage录音元数据存储
i18n-js中英文国际化
react-native-reanimated动画
react-native-gesture-handler滑动删除

核心模块 1:录音 Hook(use-recording.ts)

这是整个 App 的核心,~250 行。

录音配置


const RECORDING_OPTIONS = {
  sampleRate: 8000,        // 8kHz(人声足够)
  numberOfChannels: 1,     // 单声道
  bitRate: 16000,          // 16kbps(极低码率)
  isMeteringEnabled: true, // 开启分贝采样
};

为什么这么低?8kHz 单声道 16kbps 约 200-300KB/分钟,录一整晚 8 小时也才 ~20MB。

分贝转换


// expo-audio 的 metering 返回 dBFS(-160 到 0)
// 映射为近似的 SPL 分贝值
const meteringToDecibel = (metering: number) => {
  const spl = metering + 90;  // dBFS + 90 ≈ SPL
  return Math.max(0, Math.min(100, spl));
};

真实世界参考:呼吸 ~10dB、耳语 ~20dB、安静房间 ~30dB、正常说话 ~50dB、打鼾 40-70dB

采样流程


每 1 秒触发一次
  → recorder.getStatus() 获取 metering 值
  → dBFS → SPL 转换
  → 生成数据点 { timestamp, decibel, isSnoring }
  → 存入 decibelDataRef(内存)
  → 每 5 秒持久化到 AsyncStorage(防崩溃丢数据)
  → App 切后台时立即保存

后台录音


await AudioModule.setAudioModeAsync({
  shouldPlayInBackground: true,
  allowsBackgroundRecording: true,
  interruptionMode: 'doNotMix',
});

iOS 支持后台持续录音(用户需授权)。

核心模块 2:打鼾分析(storage.ts)

打鼾检测算法(纯阈值法,无 ML):


1. 遍历每个分贝数据点
2. 超过阈值(默认 45dB)→ 开始记录事件
3. 低于阈值 → 结束事件(如果持续 ≥ 1秒)
4. 统计事件数量、总时长、最大/平均分贝

严重程度分级

等级条件
none无打鼾事件
mild打鼾时间占比 ≤ 15%,事件 ≤ 25 次
moderate打鼾时间占比 ≤ 30%,事件 ≤ 50 次
severe打鼾时间占比 > 30% 或事件 > 50 次

数据存储优化(值得学习的部分):


旧方案:所有录音(含分贝数据)存一个 JSON key
  → 列表加载慢(每次都读全部分贝数据)

新方案:分离存储
  → sleep_recordings_meta:只存元数据(列表显示用)
  → decibel_data_{id}:每个录音的分贝数据单独存储
  → 列表秒加载,详情页按需加载

还做了懒迁移:打开旧格式录音时自动转新格式,不一次性全迁移。

核心模块 3:波形回放(audio-player-chart.tsx)

~500 行的复合组件,集成了:

降采样策略:不是均匀采样,而是每个区间取最大值——确保打鼾峰值不会被漏掉。

UI 亮点

源码设计亮点

1. 防数据丢失

2. 存储分离

3. 格式迁移

4. 模拟器兼容

5. 极低资源消耗

分析

优势

风险/不足

改进方向

后台保活机制分析

snoreman 的保活策略(非常朴素)

依赖 expo-audio 自带的后台录音能力,没有做任何特殊保活:


await AudioModule.setAudioModeAsync({
  shouldPlayInBackground: true,      // 后台继续录音
  allowsBackgroundRecording: true,   // 关键开关:注册为后台音频类别
  interruptionMode: 'doNotMix',      // 其他 App 播放音频时不中断
});

数据安全措施(不是保活,是防丢数据):

它没做的

实际能撑多久?

平台8 小时(睡觉)24 小时
**Android**✅ 稳定✅ 稳定(前台 Service)
**iOS**✅ 基本没问题⚠️ 不稳定,可能被回收

iOS 的 allowsBackgroundRecording 依赖 Background Audio 机制,长时间无用户交互(12h+)可能被系统回收,低电量模式会直接杀掉。

改造为 24 小时录音 App 的保活方案

Android(简单)

1. 前台通知 + Foreground Service

2. START_STICKY 自动重启

3. 基本就够了

iOS(困难)

1. 分段录音:每 4 小时自动停一次、再自动开,重置系统计时器

2. 播放静音音频:录音同时循环播放静音 WAV,欺骗系统认为 App 在"播放音乐"

3. Background App Refresh:注册后台刷新任务,定期检查录音状态

4. 本地通知:定期弹静默通知提醒系统 App 还活着

> 方案 1+2 组合基本能稳定 24 小时,但方案 2 有被 App Store 审核拒的风险。自用不上架的话完全没问题。

改造为 24 小时录音 App 可行性

与 PLAUD Note 对比

snoreman 改造PLAUD Note
硬件手机自带麦专用磁吸设备(双麦阵列,降噪好)
收音质量一般(口袋里噪音大)专用硬件,收音远优于手机
后台录音iOS 24h 不稳定硬件自带存储,不依赖 App
AI 转录需自己接 Whisper内置 OpenAI/ChatGPT
成本免费硬件 ~$89 + 订阅

结论

评分

维度评分 (1-10)说明
创新性6打鼾监测 App 不新,但实现完整
实用性8真实可用,资源消耗低
代码质量8工程化程度高,数据安全措施到位
UI/UX8实时波形、阈值调节、点击跳转,体验好
可扩展性6纯阈值检测,缺少 ML 和趋势分析
生态469 stars,个人项目
**总分****6.7**工程质量优秀的个人健康 App,有实际使用价值