Flutter中获取麦克风语音频率问题求助(附fftea代码)
问题分析与解决
你的代码存在几个关键问题,导致无法得到正确的语音频率结果,逐个解决如下:
1. 原始音频数据转换错误
麦克风采集的PCM数据通常是16位有符号整数(int16),直接把List<int>强制转换为double会导致数值完全失真,需要先将每个int16值归一化到[-1.0, 1.0]的范围。
2. 缺少采样率参数
FFT返回的是频域的bin索引,要转换成实际频率值必须知道音频的采样率(比如常见的44100Hz、48000Hz),需要确保原生端传递音频数据时同时提供采样率。
3. 未提取有效频率值
FFT返回的是复数数组,需要计算每个bin的幅度,找到幅度最大的bin,再转换成对应的实际频率值。
4. 未更新UI
没有通过setState将计算出的频率更新到文本组件中,导致UI始终显示固定内容。
修正后的完整代码
前提:原生端调整
需要修改原生代码,让EventChannel返回包含audioData(ListsampleRate(int)的Map,确保传递采样率信息。
Flutter端修正代码
import 'dart:async'; import 'dart:math'; import 'package:fftea/impl.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Microphone Frequency Analyzer', theme: ThemeData(primarySwatch: Colors.blue), home: const AudioStreamPage(), ); } } class AudioStreamPage extends StatefulWidget { const AudioStreamPage({super.key}); @override _AudioStreamPageState createState() => _AudioStreamPageState(); } class _AudioStreamPageState extends State<AudioStreamPage> { static const _audioStreamChannel = EventChannel('audio_stream'); StreamSubscription? _streamSubscription; String _currentFrequency = '0 Hz'; int _sampleRate = 44100; // 默认采样率,实际从原生获取 @override void initState() { super.initState(); _startAudioStream(); } @override void dispose() { _streamSubscription?.cancel(); super.dispose(); } void _startAudioStream() { _streamSubscription = _audioStreamChannel.receiveBroadcastStream().listen((dynamic event) { final dataMap = event as Map<String, dynamic>; final audioData = dataMap['audioData'] as List<int>; _sampleRate = dataMap['sampleRate'] as int; analyzeAudioData(audioData); }, onError: (error) { debugPrint('音频流错误: $error'); }); } void analyzeAudioData(List<int> audioData) { // 将int16 PCM数据归一化为[-1.0, 1.0]的double数组 final doubleSamples = audioData.map((int value) { return value / 32768.0; }).toList(); // 确保FFT输入长度为2的幂(fftea要求,否则报错) final fftSize = _nextPowerOfTwo(doubleSamples.length); final paddedSamples = _padToLength(doubleSamples, fftSize); // 执行FFT final fft = FFT(fftSize); final complexResult = fft.realFft(paddedSamples); // 计算每个频域bin的幅度 final magnitudes = <double>[]; for (var i = 0; i < fftSize ~/ 2; i++) { final real = complexResult[i].real; final imag = complexResult[i].imag; magnitudes.add(sqrt(real * real + imag * imag)); } // 找到幅度最大的bin索引 int dominantBin = 0; double maxMagnitude = 0; for (var i = 1; i < magnitudes.length; i++) { if (magnitudes[i] > maxMagnitude) { maxMagnitude = magnitudes[i]; dominantBin = i; } } // 将bin索引转换为实际频率:频率 = (bin索引 * 采样率) / FFT长度 final dominantFrequency = (dominantBin * _sampleRate) / fftSize; // 更新UI setState(() { _currentFrequency = '${dominantFrequency.round()} Hz'; }); } // 找到大于等于输入值的最小2的幂 int _nextPowerOfTwo(int value) { int result = 1; while (result < value) { result <<= 1; } return result; } // 填充数组到指定长度(补0) List<double> _padToLength(List<double> list, int length) { final padded = List<double>.filled(length, 0.0); padded.setRange(0, min(list.length, length), list); return padded; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('麦克风频率分析仪')), body: Center( child: Text( _currentFrequency, style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold), ), ), ); } }
额外注意事项
- 原生端必须确保传递的是16位有符号整数格式的PCM数据,采样率要和实际采集一致。
- 实际场景中可以给音频数据加汉明窗,减少频谱泄漏,提升频率检测的准确性。
- 如果需要更稳定的结果,可以对多次FFT的结果做平均处理。
内容的提问来源于stack exchange,提问作者Yash
相关产品推荐
相关产品推荐

