如何从.wav心音文件计算心率(BPM) 安卓端实现技术咨询
安卓端心音音频计算心率BPM的可行实现方案
注:以下方案均基于你已能采集到清晰可分辨S1/S2心音信号的前提。
方案1:峰值检测法(最易实现,适合清晰心音场景)
- 核心逻辑:心音的S1(lub)信号能量远高于周围杂音与S2信号,统计固定时间窗口内的S1峰值数量即可直接换算为BPM
- 安卓端实现步骤:
- 先将录制的音频文件解码为PCM幅值数组:如果是MP3/AAC等压缩格式,用
MediaExtractor+MediaCodec解码即可;如果录制的是原始PCM文件,直接读取内容转Short数组即可 - 使用动态阈值判断峰值:取前1秒音频的平均幅值的3~5倍作为阈值,避免不同录制音量导致的误判
- 峰值去重:两个S1信号的最短间隔不会小于300ms(对应最高心率200BPM),间隔小于该值的峰值直接过滤,避免将S2误判为S1
- 统计10~30秒窗口内的有效峰值数,按比例换算为每分钟的数值即为BPM
- 先将录制的音频文件解码为PCM幅值数组:如果是MP3/AAC等压缩格式,用
- 核心代码示例(Kotlin):
// 输入PCM幅值数组、采样率,返回计算得到的BPM fun calculateBpmFromPcm(pcmShorts: ShortArray, sampleRate: Int): Int { // 取前1秒平均幅值的4倍作为动态阈值 val threshold = pcmShorts.take(sampleRate).average() * 4 val peakIndices = mutableListOf<Int>() // 300ms最短峰值间隔,对应最高200BPM val minPeakDistance = (sampleRate * 0.3).toInt() for (i in 1 until pcmShorts.size - 1) { // 判断当前点是否为局部峰值且超过阈值 if (pcmShorts[i] > threshold && pcmShorts[i] > pcmShorts[i-1] && pcmShorts[i] > pcmShorts[i+1]) { if (peakIndices.isEmpty() || i - peakIndices.last() >= minPeakDistance) { peakIndices.add(i) } } } // 换算为每分钟的峰值数 val durationSeconds = pcmShorts.size.toFloat() / sampleRate return (peakIndices.size * 60 / durationSeconds).toInt() }
提示:你可以根据你的听诊器硬件的实际录制效果调整阈值倍数和最小峰值间隔,适配不同用户的心率范围。
- 优势:实现成本极低,运算量极小,适合移动端离线/实时计算,心音清晰时准确率可达95%以上。
方案2:自相关分析法(抗干扰能力更强,适合有轻微杂音的场景)
- 核心逻辑:心音是强周期性信号,通过自相关运算可以精准找到信号的周期长度,直接换算为心率
- 实现步骤:
- 解码得到PCM幅值数组后做归一化处理
- 对数组做自相关运算,找到自相关系数最大的延迟点,该延迟值即为心音的周期长度
- 用60除以周期长度(单位为秒)即可得到BPM
- 优势:无需手动调整阈值,抗轻微杂音能力优于峰值检测,运算量中等,安卓端可流畅运行。
方案3:轻量机器学习推理(适合复杂场景的高准确率需求)
- 核心逻辑:如果你的应用需要兼容有背景杂音、心音较弱的场景,可以部署轻量端侧模型识别S1信号
- 实现步骤:
- 用公开心音数据集训练二分类模型,识别音频片段是否为S1信号,训练完成后导出为TFLite格式
- 安卓端引入
tensorflow-lite依赖加载模型,对输入的音频片段做推理 - 统计固定时间内识别到的S1数量,换算得到BPM
- 优势:复杂场景下准确率远高于传统信号处理方案,模型大小可控制在1MB以内,不会过多占用应用体积。
内容的提问来源于stack exchange,提问作者Rahul Pareta
相关产品推荐
相关产品推荐

