You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过麦克风插孔用JavaScript读取指夹式血氧仪数据并流至iframe

如何通过麦克风接口读取指夹式血氧仪数据并在iframe中显示?

我有一台旧的指夹式血氧仪(夹拇指的款式),希望将其接入电脑的麦克风插孔,通过getUserMedia API将数据流式传输至iframe中。以下是我目前编写的JavaScript代码:

window.AudioContext = window.AudioContext || window.webkitAudioContext; 
const context = new AudioContext(); 
navigator.mediaDevices.getUserMedia({audio: true}).then((stream) => { 
  const microphone = context.createMediaStreamSource(stream); 
  const filter = context.createBiquadFilter(); 
  microphone.connect(filter); 
  var frame = createEle("iframe");//createEle是我库中的创建元素函数
  frame.sandbox = "allow-scripts"; 
  updateFrame(); 
  function updateFrame(){ 
    setTimeout(function(){ 
      frame.srcdoc = filter.context.listener.value;//我希望iframe显示血氧仪数值并每秒刷新
      console.log(filter.context.listener); 
      updateFrame(); 
    },1000); 
  } 
  heartPage.append(frame);//heartPage是页面上的静态元素
});

目前无法正确获取血氧仪数值并在iframe中实现预期的刷新显示,恳请技术指导。


你的代码目前存在几个核心问题,导致无法获取血氧仪数据,我来一步步帮你理清思路并给出解决方案:

核心问题分析

  • 完全误解了AudioContext API的用途:你代码里的filter.context.listener是Web Audio API中用于3D音频空间定位的Listener对象,和血氧仪的生理数据没有任何关系,它根本不会包含你需要的血氧/心率数值,这是最根本的错误。
  • 对血氧仪的输出信号认知有误:旧指夹式血氧仪通过麦克风接口输出的不是直接的数字数值,而是调制后的PPG(光电容积脉搏波)音频信号——血氧仪通过红光和红外光照射手指,检测血液流动带来的光强变化,把这个变化转换成音频波形输出,你需要先解析这个波形才能得到血氧饱和度和心率。

正确的实现步骤

要完成你的需求,需要分三步:采集原始音频信号、解析PPG信号计算生理数据、将数据传递给iframe显示。下面是具体的代码示例和说明:

1. 采集原始音频信号

首先要关闭浏览器默认的音频降噪、回声消除等功能,避免干扰原始信号;然后用ScriptProcessorNode(或现代的AudioWorklet)获取音频缓冲区的原始数据:

window.AudioContext = window.AudioContext || window.webkitAudioContext;
const context = new AudioContext();
const sampleRate = context.sampleRate;

// 请求音频权限,关闭所有音频处理功能,保留原始信号
navigator.mediaDevices.getUserMedia({
  audio: {
    echoCancellation: false,
    noiseSuppression: false,
    autoGainControl: false,
    latency: 0 // 尽可能降低延迟
  }
}).then((stream) => {
  const microphone = context.createMediaStreamSource(stream);
  
  // 创建ScriptProcessorNode获取原始音频数据(注意:该API已被标记为deprecated,推荐用AudioWorklet)
  const processor = context.createScriptProcessor(4096, 1, 1);
  microphone.connect(processor);
  processor.connect(context.destination);
  
  // 创建并插入iframe
  const frame = createEle("iframe");
  frame.sandbox = "allow-scripts allow-same-origin"; // 增加权限以便postMessage通信
  heartPage.append(frame);
  
  // 用于缓存音频数据,累计1秒的采样后再处理
  let signalBuffer = [];

  // 处理音频数据的回调
  processor.onaudioprocess = function(e) {
    const inputData = e.inputBuffer.getChannelData(0);
    // 将当前缓冲区的原始音频数据加入缓存
    signalBuffer = signalBuffer.concat(Array.from(inputData));
    
    // 当缓存了1秒的音频数据时,进行处理
    if (signalBuffer.length >= sampleRate * 1) {
      // 解析PPG信号,计算血氧和心率
      const vitalData = processPPGSignal(signalBuffer);
      // 用postMessage给iframe发送数据(比直接设置srcdoc更高效)
      frame.contentWindow.postMessage(vitalData, '*');
      // 清空缓存,准备下一轮采集
      signalBuffer = [];
    }
  };

  // ------------------------------
  // 核心:PPG信号解析函数(需要你根据血氧仪的实际信号调整)
  // ------------------------------
  function processPPGSignal(rawSignal) {
    // 第一步:对原始信号滤波,去除高频噪声和基线漂移
    const filteredSignal = lowPassFilter(rawSignal, sampleRate, 10); // 保留10Hz以下的信号
    
    // 第二步:检测脉搏波的峰值,计算心率
    const peaks = detectPeaks(filteredSignal, sampleRate);
    const heartRate = peaks.length * 60; // 1秒内的峰值数*60就是心率
    
    // 第三步:计算血氧饱和度(需要区分红光和红外光的信号,取决于血氧仪的输出方式)
    // 如果血氧仪是双声道输出红/红外信号,需要分离两个通道计算AC/DC比值;如果是单声道调制,需要解析不同频率成分
    // 这里先模拟合理数值,你需要根据实际信号调整
    const spo2 = Math.max(90, Math.min(100, 95 + Math.random() * 5));
    
    return { spo2, heartRate };
  }

  // 简单的低通滤波实现(示例)
  function lowPassFilter(signal, sampleRate, cutoff) {
    const rc = 1 / (2 * Math.PI * cutoff);
    const dt = 1 / sampleRate;
    const alpha = dt / (rc + dt);
    let filtered = [signal[0]];
    for (let i = 1; i < signal.length; i++) {
      filtered[i] = filtered[i-1] + alpha * (signal[i] - filtered[i-1]);
    }
    return filtered;
  }

  // 峰值检测实现(示例)
  function detectPeaks(signal, sampleRate) {
    const peaks = [];
    const threshold = 0.5 * Math.max(...signal); // 用信号最大值的50%作为阈值
    for (let i = 1; i < signal.length - 1; i++) {
      if (signal[i] > threshold && signal[i] > signal[i-1] && signal[i] > signal[i+1]) {
        peaks.push(i);
      }
    }
    return peaks;
  }

  // 初始化iframe的内容,用于接收并显示数据
  frame.srcdoc = `
  <html>
  <body style="font-size:24px; padding:20px;">
    <p>血氧饱和度: <span id="spo2">--</span>%</p>
    <p>心率: <span id="heartRate">--</span> BPM</p>
  </body>
  <script>
  window.addEventListener('message', (e) => {
    document.getElementById('spo2').textContent = e.data.spo2;
    document.getElementById('heartRate').textContent = e.data.heartRate;
  });
  </script>
  </html>
  `;
});

2. 关键注意事项

  • 先分析血氧仪的信号:不同旧款血氧仪的信号格式差异很大,你可以先用Audacity等音频软件录制一段血氧仪工作时的音频,观察波形和频谱,确定它的调制方式(比如是否是双声道输出红/红外信号,还是用不同频率调制在单声道里),这是实现正确解析的前提。
  • 使用AudioWorklet替代ScriptProcessorNode:ScriptProcessorNode已经被弃用,在现代浏览器中推荐使用AudioWorklet来处理音频,它运行在独立的线程中,不会阻塞主线程,性能更好。
  • 信号处理算法:PPG信号的解析需要专业的信号处理知识,如果你不想自己实现,可以找一些开源的PPG处理库来简化开发。

内容的提问来源于stack exchange,提问作者MrEhawk82

火山引擎 最新活动