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

如何在Flutter中实现URL实时音频流播放与波形可视化?

Flutter实时音频流波形可视化实现方案

一、用现有包快速实现(推荐)

优先选择支持PCM数据回调的音频播放库,flutter_sound是最适配的方案,它内置了流式音频处理和PCM提取能力,无需重复造轮子。

步骤1:集成依赖

在pubspec.yaml中添加:

dependencies:
  flutter_sound: ^9.2.13

步骤2:流式播放并提取PCM

初始化播放器,设置音频流URL,通过回调获取PCM数据并计算振幅:

import 'package:flutter_sound/flutter_sound.dart';
import 'package:flutter/material.dart';

class AudioWaveformPage extends StatefulWidget {
  const AudioWaveformPage({super.key});

  @override
  State<AudioWaveformPage> createState() => _AudioWaveformPageState();
}

class _AudioWaveformPageState extends State<AudioWaveformPage> {
  final FlutterSoundPlayer _player = FlutterSoundPlayer();
  List<double> _waveformAmplitudes = [];

  @override
  void initState() {
    super.initState();
    _initAudioPlayer();
  }

  Future<void> _initAudioPlayer() async {
    await _player.openAudioSession();
    // 注册PCM数据处理回调
    _player.setAudioProcessor(_AudioProcessor((pcmData) {
      // 计算当前帧平均振幅,更新波形数据
      final avgAmplitude = pcmData.map((v) => v.abs()).reduce((a, b) => a + b) / pcmData.length;
      setState(() {
        _waveformAmplitudes.add(avgAmplitude);
        // 限制波形点数,避免内存溢出
        if (_waveformAmplitudes.length > 500) {
          _waveformAmplitudes.removeAt(0);
        }
      });
    }));
    // 启动流式音频播放(替换为你的音频流URL)
    await _player.startPlayer(
      fromURI: 'https://example.com/stream-audio.mp3',
      codec: Codec.mp3,
    );
  }

  @override
  void dispose() {
    _player.closeAudioSession();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Expanded(child: WaveformWidget(amplitudes: _waveformAmplitudes)),
        ],
      ),
    );
  }
}

// 自定义音频处理器,接收PCM数据
class _AudioProcessor extends AudioProcessor {
  final Function(List<int>) onPcmData;

  _AudioProcessor(this.onPcmData);

  @override
  void processAudioFrames(AudioFrames frames) {
    // frames.samples为Int16List格式的PCM采样数据
    onPcmData(frames.samples);
  }
}

二、自定义流式音频处理(进阶)

如果需要更底层的控制,可通过dart:io获取音频流,结合flutter_ffmpeg解码为PCM:

步骤1:集成依赖

dependencies:
  flutter_ffmpeg: ^0.4.0

步骤2:解码音频流并提取PCM

import 'dart:io';
import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';

void _processAudioStream(String url) async {
  final client = HttpClient();
  final request = await client.getUrl(Uri.parse(url));
  final response = await request.close();

  // 使用FFmpeg将音频流解码为16位单声道PCM
  final ffmpeg = FlutterFFmpeg();
  final arguments = [
    '-i', 'pipe:0', // 从标准输入读取流
    '-f', 's16le',
    '-ac', '1',
    '-ar', '44100',
    'pipe:1' // 输出到标准输出
  ];

  final process = await ffmpeg.executeWithArguments(arguments);
  if (process == 0) {
    // 读取解码后的PCM字节流,转换为Int16List
    await for (final chunk in response) {
      final pcm = chunk.buffer.asInt16List();
      // 计算振幅并更新波形(同之前的逻辑)
      _updateWaveform(pcm);
    }
  }
}

三、实时波形绘制

用CustomPaint实现自定义波形绘制,灵活控制样式:

import 'package:flutter/material.dart';

class WaveformWidget extends StatelessWidget {
  final List<double> amplitudes;

  const WaveformWidget({super.key, required this.amplitudes});

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: const Size(double.infinity, 120),
      painter: _WaveformPainter(amplitudes: amplitudes),
    );
  }
}

class _WaveformPainter extends CustomPainter {
  final List<double> amplitudes;
  final Paint _paint = Paint()
    ..color = Colors.blueAccent
    ..strokeWidth = 2
    ..style = PaintingStyle.stroke;

  _WaveformPainter({required this.amplitudes});

  @override
  void paint(Canvas canvas, Size size) {
    if (amplitudes.isEmpty) return;

    final widthPerPoint = size.width / amplitudes.length;
    final maxPcmValue = 32767; // 16位PCM的最大值
    final amplitudeScale = size.height / maxPcmValue;

    // 绘制连续波形线
    for (int i = 0; i < amplitudes.length - 1; i++) {
      final x1 = i * widthPerPoint;
      final y1 = size.height / 2 - amplitudes[i] * amplitudeScale;
      final x2 = (i + 1) * widthPerPoint;
      final y2 = size.height / 2 - amplitudes[i + 1] * amplitudeScale;
      canvas.drawLine(Offset(x1, y1), Offset(x2, y2), _paint);
    }
  }

  @override
  bool shouldRepaint(covariant _WaveformPainter oldDelegate) {
    return oldDelegate.amplitudes != amplitudes;
  }
}

关键注意事项

  • 性能优化:避免处理每个PCM采样点,按帧计算平均振幅减少绘制数据量;音频处理逻辑放在后台线程(可使用Isolate或compute),避免阻塞UI。
  • 格式适配:确保解码器参数与音频流格式匹配(如采样率、声道数、编码格式)。
  • 权限配置:Android需添加<uses-permission android:name="android.permission.INTERNET"/>,iOS需在Info.plist中开启网络访问权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 00:13:17