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

如何在Flutter Flame中播放动态生成的原始二进制声音?

动态生成PCM音频并播放的解决方案

方案一:使用flutter_sound流式播放

你之前尝试startPlayerFromStream失败,大概率是音频参数配置不正确或未正确处理流式数据推送。以下是完整可运行示例:

  1. 添加依赖:
dependencies:
  flutter_sound: ^9.2.13
  audio_session: ^0.1.16+1 # 用于配置音频会话权限与策略
  1. 核心音频播放类:
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:audio_session/audio_session.dart';

class DynamicAudioPlayer {
  FlutterSoundPlayer? _player;
  bool _isStreamActive = false;

  Future<void> init() async {
    // 配置音频会话,确保系统允许播放
    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration(
      avAudioSessionCategory: AVAudioSessionCategory.playback,
      avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.mixWithOthers,
      androidAudioAttributes: const AndroidAudioAttributes(
        contentType: AndroidAudioContentType.music,
        usage: AndroidAudioUsage.media,
      ),
    ));

    _player = FlutterSoundPlayer();
    await _player!.openPlayer();
  }

  // 推送动态生成的PCM数据到播放流
  Future<void> pushPCMData(List<int> pcmData, {int sampleRate = 44100}) async {
    if (_player == null || !_player!.isOpen) return;

    // 首次推送时启动流播放
    if (!_isStreamActive) {
      await _player!.startPlayerFromStream(
        sampleRate: sampleRate,
        numChannels: 1,
        codec: Codec.pcm16, // 对应16bit单声道PCM
      );
      _isStreamActive = true;
    }

    // 将List<int>转为插件要求的Uint8List格式
    await _player!.feedFromStream(Uint8List.fromList(pcmData));
  }

  Future<void> stop() async {
    await _player?.stopPlayer();
    await _player?.closePlayer();
    _player = null;
    _isStreamActive = false;
  }
}
  1. 在Flame游戏中调用(必须切换到UI线程):
import 'package:flame/game.dart';

class C64SimGame extends FlameGame {
  late DynamicAudioPlayer audioPlayer;

  @override
  Future<void> onLoad() async {
    super.onLoad();
    audioPlayer = DynamicAudioPlayer();
    await audioPlayer.init();
  }

  @override
  void update(double dt) {
    super.update(dt);
    // 动态生成模拟C64风格的PCM音频(示例用正弦波)
    final pcmData = generateC64StyleSample();

    // 所有插件调用必须在UI线程执行,通过addPostFrameCallback切换
    WidgetsBinding.instance.addPostFrameCallback((_) {
      audioPlayer.pushPCMData(pcmData);
    });
  }

  // 生成16bit单声道PCM示例数据
  List<int> generateC64StyleSample({int sampleRate = 44100}) {
    final sampleCount = (sampleRate * 0.01).toInt(); // 10ms片段
    final List<int> pcm = [];
    // 模拟C64的方波+噪声混合(这里简化为正弦波)
    const double frequency = 220.0;
    for (int i = 0; i < sampleCount; i++) {
      final double t = i / sampleRate;
      final double value = sin(2 * pi * frequency * t);
      // 转换为16bit PCM范围(-32768到32767),转为小端字节序
      final int intValue = (value * 32767).toInt();
      pcm.add(intValue & 0xFF);
      pcm.add((intValue >> 8) & 0xFF);
    }
    return pcm;
  }

  @override
  void onDestroy() {
    audioPlayer.stop();
    super.onDestroy();
  }
}

方案二:使用just_audio的流式AudioSource

just_audio支持通过AudioSource.fromStream创建流式数据源,你之前可能忽略了这个API:

  1. 添加依赖:
dependencies:
  just_audio: ^0.9.34
  audio_session: ^0.1.16+1
  1. 核心代码:
import 'package:just_audio/just_audio.dart';
import 'package:audio_session/audio_session.dart';

class JustAudioStreamPlayer {
  late AudioPlayer _player;
  late StreamController<PCMData> _pcmStreamController;

  Future<void> init() async {
    final session = await AudioSession.instance;
    await session.configure(const AudioSessionConfiguration.music());

    _player = AudioPlayer();
    _pcmStreamController = StreamController<PCMData>();

    // 创建流式音频源
    final source = AudioSource.fromStream(
      _pcmStreamController.stream,
      codec: const Codec.pcm16WAV(),
      sampleRate: 44100,
      channelCount: 1,
    );

    await _player.setAudioSource(source);
    await _player.play(); // 提前启动播放,等待数据流推送
  }

  void pushPCMData(List<int> pcmData) {
    if (!_pcmStreamController.isClosed) {
      _pcmStreamController.add(PCMData(Uint8List.fromList(pcmData)));
    }
  }

  Future<void> dispose() async {
    await _pcmStreamController.close();
    await _player.dispose();
  }
}
  1. Flame中调用逻辑与方案一一致,需通过WidgetsBinding.instance.addPostFrameCallback切换到UI线程推送数据。

关键注意事项

  • 线程切换:Flame的update方法运行在游戏专用线程,所有与音频插件的交互必须切换到UI线程执行,否则会出现跨线程调用错误。
  • PCM格式匹配:确保生成的PCM数据与插件配置的采样率、声道数、位深完全一致(比如16bit PCM必须是小端字节序)。
  • 资源释放:游戏销毁时必须调用音频播放器的停止/销毁方法,避免内存泄漏。

内容的提问来源于stack exchange,提问作者christian.stock

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 03:17:47