You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何通过麦克风流识别音频音阶?

Flutter 麦克风流音频频率提取与音阶匹配问题

问题背景

我在Flutter项目中使用mic_stream包监听麦克风流,目标是识别音频对应的音阶。已知核心思路是获取音频频率后匹配音阶表,但当前代码计算出的结果范围异常,无法有效获取频率:

  • 流长度固定为8192
  • 采样值求和结果在90-106区间
  • 现有公式计算出的频率值在0.0013-0.0019区间,播放不同歌曲时数值无明显变化

现有代码

Future<bool> _startListening() async {
  MicStream.shouldRequestPermission(true);
  stream = await MicStream.microphone(
      audioSource: AudioSource.DEFAULT,
      sampleRate: 48000,
      channelConfig: ChannelConfig.CHANNEL_IN_MONO,
      audioFormat: AudioFormat.ENCODING_PCM_16BIT);

  print(
      "Start Listening to the microphone, sample rate is ${await MicStream.sampleRate}, bit depth is ${await MicStream.bitDepth}, bufferSize: ${await MicStream.bufferSize}");
  bytesPerSample = (await MicStream.bitDepth)! ~/ 8;
  samplesPerSecond = (await MicStream.sampleRate)!.toInt();
  localMax = null;
  localMin = null;

  setState(() {
    _frequency = samplesPerSecond;
    isRecording = true;
    startTime = DateTime.now();
  });
  visibleSamples = [];
  listener = stream!.listen(_calculateSamples);
  StreamSubscription<List<int>> _listener =
      stream!.listen((samples) => _textFrequency = [
            'Total: ${samples.length}',
            'Added: ${samples.reduce((a, b) => a + b)}',
            'Added Frequency: ${int.parse(samples.reduce((a, b) => a + b).toString()) / (65536 * 10000)}'
          ].toString());

  return true;
}

问题分析

当前代码的频率计算逻辑完全错误:

  • 直接对原始PCM采样值求和没有任何频率意义,PCM值是音频波形的振幅采样,求和结果仅能反映整体能量的大致情况,和频率无关
  • 后续的除法公式也没有理论依据,自然无法得到有效频率值

解决方案步骤

1. 正确解析PCM采样数据

由于使用的是ENCODING_PCM_16BIT,每个采样是2字节的有符号整数,需要先把字节流转换成16位整数数组:

List<int> convertBytesToInt16(List<int> bytes) {
  List<int> int16Samples = [];
  for (int i = 0; i < bytes.length; i += 2) {
    // 小端模式解析16位有符号整数(多数设备采用)
    int sample = ((bytes[i + 1] << 8) | bytes[i]) - 32768;
    int16Samples.add(sample);
  }
  return int16Samples;
}

2. 使用FFT计算频率

要提取音频的基频,需要对采样数据进行快速傅里叶变换(FFT)。可以使用audio_fft包实现:

  • 添加依赖到pubspec.yaml:
dependencies:
  audio_fft: ^0.0.1 # 可替换为最新版本
  • 在监听流中执行FFT:
StreamSubscription<List<int>> _listener = stream!.listen((bytes) {
  List<int> samples = convertBytesToInt16(bytes);
  // 执行FFT计算幅度谱
  final fft = AudioFFT();
  final magnitudes = fft.computeFFT(samples);
  // 找到幅度最大的频率点索引
  int maxIndex = magnitudes.indexOf(magnitudes.reduce((a, b) => a > b ? a : b));
  // 计算对应的实际频率:频率 = (最大索引 × 采样率) / 采样点数
  double frequency = (maxIndex * samplesPerSecond) / samples.length;
  _textFrequency = [
    '采样点数: ${samples.length}',
    '基频: ${frequency.toStringAsFixed(2)} Hz'
  ].toString();
  setState(() {});
});

3. 频率匹配音阶表

定义标准音阶频率表(以A4=440Hz为基准),计算当前频率与各音阶的误差,取误差最小的作为识别结果:

// 标准音阶频率表(示例,可扩展其他八度)
final Map<String, double> noteFrequencies = {
  'C4': 261.63,
  'C#4': 277.18,
  'D4': 293.66,
  'D#4': 311.13,
  'E4': 329.63,
  'F4': 349.23,
  'F#4': 369.99,
  'G4': 392.00,
  'G#4': 415.30,
  'A4': 440.00,
  'A#4': 466.16,
  'B4': 493.88,
};

String matchNote(double frequency) {
  if (frequency < 20) return '无有效音频'; // 过滤低频噪声
  double minError = double.infinity;
  String matchedNote = '';
  noteFrequencies.forEach((note, freq) {
    double error = (frequency - freq).abs();
    if (error < minError) {
      minError = error;
      matchedNote = note;
    }
  });
  return matchedNote;
}

4. 优化建议

  • 噪声过滤:忽略幅度低于阈值的频率点,避免环境噪声干扰
  • 平滑处理:对连续几次识别的结果取平均值,提升稳定性
  • 采样参数调整:如果48000采样率计算压力大,可尝试降低到22050或16000,减少计算量

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:44:59