snoreman 源码解析 — 用手机录鼾声并分析的 React Native App
> 一句话版本:一个 React Native 手机 App,睡觉时开着录鼾声,每秒采样一次分贝值,超过阈值就标记为"打鼾",第二天看波形图、统计数据和严重程度评估。
| 项目 | 信息 | |
|---|---|---|
| 来源 | https://github.com/timqian/snoreman | |
| 作者 | timqian(中国开发者) | |
| 创建时间 | 2026-01-20 | |
| 语言 | TypeScript / React Native (Expo) | |
| Stars | 69 | Forks 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 行的复合组件,集成了:
- 波形图:竖线条形图,紫色=正常,红色=超阈值
- 播放器:播放/暂停、进度滑块、快进快退 15 秒
- 阈值滑块:右侧垂直滑块调节阈值,实时重新着色波形
- 点击跳转:点击红色波形条自动跳转播放(提前 2 秒,听到完整呼噜声)
- 降采样:最多渲染 400 个数据点(8 小时录音 = 28800 个数据点,需要降采样)
降采样策略:不是均匀采样,而是每个区间取最大值——确保打鼾峰值不会被漏掉。
UI 亮点
- 实时录音时:大号分贝数字 + 绿/红状态指示 + 实时波形条 + 阈值调节
- 录音列表:左滑删除、长按删除、严重程度彩色标签
- 详情页:波形图 + 播放器 + 统计卡片 + 导出/删除
- 横竖屏切换(详情页)
- 深色/浅色模式跟随系统
- 中英文自动切换
源码设计亮点
1. 防数据丢失
- 每 5 秒定期保存到 AsyncStorage
- App 切后台时立即保存
- 崩溃重启可恢复
2. 存储分离
- 元数据和分贝数据分开存储
- 列表页秒加载,详情页按需加载
3. 格式迁移
- 旧格式(完整 URI)→ 新格式(相对路径)
- 旧存储(单一 JSON)→ 新存储(分离存储)
- 懒迁移:访问时才转,不一次性全转
4. 模拟器兼容
- iOS 模拟器无法获取真实 metering 数据
- 开发环境自动生成模拟数据(25-40dB 基础噪音 + 10% 概率模拟打鼾)
5. 极低资源消耗
- 8kHz 单声道 16kbps → 200-300KB/分钟
- 8 小时录音仅 ~20MB
- 每 1 秒采样一次(不是每帧)
分析
优势:
- 实用性强:解决真实需求(打鼾监测)
- 工程质量高:数据防丢失、存储分离、懒迁移、降采样
- 用户体验好:实时波形、阈值调节、点击跳转呼噜声
- 跨平台:Expo SDK 54,iOS/Android/Web
- 国际化:中英文自动切换
- 资源消耗极低
风险/不足:
- 纯阈值检测,无 ML(无法区分鼾声和环境噪音)
- 69 stars,个人项目,可能停更
- 后台录音依赖系统权限,iOS 限制较多
- 无云同步、无长期趋势分析
- 无法区分打鼾类型(OSA/CSA/混合)
改进方向:
- 用轻量音频分类模型(如 YAMNet)替代纯阈值
- 加入睡眠分期(深睡/浅睡/REM 对应不同鼾声模式)
- 长期趋势图表(周/月打鼾频率变化)
- 与 Apple Health / Google Fit 集成
后台保活机制分析
snoreman 的保活策略(非常朴素)
依赖 expo-audio 自带的后台录音能力,没有做任何特殊保活:
await AudioModule.setAudioModeAsync({
shouldPlayInBackground: true, // 后台继续录音
allowsBackgroundRecording: true, // 关键开关:注册为后台音频类别
interruptionMode: 'doNotMix', // 其他 App 播放音频时不中断
});
数据安全措施(不是保活,是防丢数据):
- App 切后台时立即把内存中的分贝数据写入 AsyncStorage
- 每 5 秒定期保存
- 崩溃重启可恢复已保存的数据
它没做的
- ❌ 前台 Service / 通知栏常驻
- ❌ 静音音频播放保活
- ❌ Location / PushKit / VoIP 保活
- ❌ 心跳检测 + 自动重启
实际能撑多久?
| 平台 | 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 + 订阅 |
结论
- 功能上:完全可行,改动量 ~30%(去掉打鼾检测 + 加文件分段 + 接 AI 转录)
- 作为 PLAUD 替代:硬件差距没法补——手机放口袋录会议,噪音远大于专用设备
- 最佳定位:Android 自用的免费 24h 录音 + AI 转录工具
评分
| 维度 | 评分 (1-10) | 说明 |
|---|---|---|
| 创新性 | 6 | 打鼾监测 App 不新,但实现完整 |
| 实用性 | 8 | 真实可用,资源消耗低 |
| 代码质量 | 8 | 工程化程度高,数据安全措施到位 |
| UI/UX | 8 | 实时波形、阈值调节、点击跳转,体验好 |
| 可扩展性 | 6 | 纯阈值检测,缺少 ML 和趋势分析 |
| 生态 | 4 | 69 stars,个人项目 |
| **总分** | **6.7** | 工程质量优秀的个人健康 App,有实际使用价值 |