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

Flutter中使用Just Audio播放原始音频流报错求助

Flutter 字节流音频播放错误排查

错误信息

播放错误
E/ExoPlayerImplInternal(19173):   com.google.android.exoplayer2.ExoPlaybackException: Source error
E/ExoPlayerImplInternal(19173):       at com.google.android.exoplayer2.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:632)
E/ExoPlayerImplInternal(19173):       at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:602)
E/ExoPlayerImplInternal(19173):       at android.os.Handler.dispatchMessage(Handler.java:98)
E/ExoPlayerImplInternal(19173):       at android.os.Looper.loop(Looper.java:154)
E/ExoPlayerImplInternal(19173):       at android.os.HandlerThread.run(HandlerThread.java:61)
E/ExoPlayerImplInternal(19173):   Caused by: com.google.android.exoplayer2.source.UnrecognizedInputFormatException: None of the available extractors (Mp3Extractor, FlvExtractor, FlacExtractor, WavExtractor, FragmentedMp4Extractor, Mp4Extractor, AmrExtractor, PsExtractor, OggExtractor, TsExtractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, Ac4Extractor, AviExtractor, JpegExtractor) could read the stream.
E/ExoPlayerImplInternal(19173):       at com.google.android.exoplayer2.source.BundledExtractorsAdapter.init(BundledExtractorsAdapter.java:92)
E/ExoPlayerImplInternal(19173):       at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1017)
E/ExoPlayerImplInternal(19173):       at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:412)
E/ExoPlayerImplInternal(19173):       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
E/ExoPlayerImplInternal(19173):       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
E/ExoPlayerImplInternal(19173):       at java.lang.Thread.run(Thread.java:762)
E/AudioPlayer(19173): TYPE_SOURCE: None of the available extractors (Mp3Extractor, FlvExtractor, FlacExtractor, WavExtractor, FragmentedMp4Extractor, Mp4Extractor, AmrExtractor, PsExtractor, OggExtractor, TsExtractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, Ac4Extractor, AviExtractor, JpegExtractor) could read the stream.

核心问题

  1. 格式不匹配:mic_stream获取的是原始PCM音频数据(无容器或编码格式),但你在MyCustomSource中声明contentType: 'audio/mpeg',ExoPlayer会尝试用MP3解析器读取数据,自然无法识别原始PCM。
  2. Socket接收逻辑错误:每次Socket收到数据就调用player.setAudioSource和player.play,会不断重置音频源,播放器每次仅拿到一小段不完整的音频片段,无法完成格式识别。

播放端代码

void initAudio() async {
    final server = await ServerSocket.bind(InternetAddress.anyIPv4, port);
    final player = AudioPlayer();
    final session = await AudioSession.instance;
    await session.configure(const AudioSessionConfiguration.speech());
    await session.setActive(true);
    server.listen((socket) {
      // event is a socket
      socket.listen((Uint8List data) async {
        print(data);
        player.setAudioSource(MyCustomSource(data), preload: false);
        player.play();
      });
    });
}

自定义音频源代码

// Feed your own stream of bytes into the player
class MyCustomSource extends StreamAudioSource {
  final List<int> bytes;
  MyCustomSource(this.bytes);

  @override
  Future<StreamAudioResponse> request([int? start, int? end]) async {
    start ??= 0;
    end ??= bytes.length;
    return StreamAudioResponse(
      sourceLength: bytes.length,
      contentLength: end - start,
      offset: start,
      stream: Stream.value(bytes.sublist(start, end)),
      contentType: 'audio/mpeg',
    );
  }
}

发送端代码

void recordAudio() async {
    print("recording and sampling");
    final socket = await Socket.connect(a5atHome, port);
    Stream<Uint8List>? stream = await MicStream.microphone(sampleRate: 44100);
    socket.addStream(stream!);

    StreamSubscription<List<int>> listener = stream.listen((samples) async {
      print(samples);
    });
}

解决方案

方案一:将PCM封装为WAV格式播放

WAV是带头部信息的PCM容器,ExoPlayer的WavExtractor可以识别这种格式,步骤如下:

  1. 接收端拼接PCM流并添加WAV头部:

    • 创建StreamController收集Socket传来的所有PCM数据,合并为连续流
    • 给合并后的PCM流添加WAV头部(需匹配采样率、声道数、位深等参数,mic_stream默认是16位单声道)
    • 修改MyCustomSource的contentType为'audio/wav'
  2. 修改后的播放端逻辑:

void initAudio() async {
  final server = await ServerSocket.bind(InternetAddress.anyIPv4, port);
  final player = AudioPlayer();
  final session = await AudioSession.instance;
  await session.configure(const AudioSessionConfiguration.speech());
  await session.setActive(true);

  server.listen((socket) {
    // 用StreamController收集连续的PCM数据
    final controller = StreamController<List<int>>();
    socket.listen((Uint8List data) {
      controller.add(data);
    }, onDone: () {
      controller.close();
    });

    // 封装为WAV格式的音频源
    final wavSource = WavStreamSource(controller.stream, sampleRate: 44100, channels: 1, bitDepth: 16);
    player.setAudioSource(wavSource, preload: false);
    player.play();
  });
}

// 自定义WAV流音频源
class WavStreamSource extends StreamAudioSource {
  final Stream<List<int>> pcmStream;
  final int sampleRate;
  final int channels;
  final int bitDepth;

  WavStreamSource(this.pcmStream, {required this.sampleRate, required this.channels, required this.bitDepth});

  @override
  Future<StreamAudioResponse> request([int? start, int? end]) async {
    // 生成WAV头部
    final header = _buildWavHeader(sampleRate, channels, bitDepth);
    // 合并头部和PCM流
    final combinedStream = Stream.value(header).followedBy(pcmStream);

    return StreamAudioResponse(
      sourceLength: null, // 流数据长度未知
      contentLength: null,
      offset: 0,
      stream: combinedStream,
      contentType: 'audio/wav',
    );
  }

  List<int> _buildWavHeader(int sampleRate, int channels, int bitDepth) {
    final byteRate = sampleRate * channels * bitDepth ~/ 8;
    final blockAlign = channels * bitDepth ~/ 8;
    final header = <int>[
      // RIFF标志
      0x52, 0x49, 0x46, 0x46,
      // 文件长度(暂填0,因为是流)
      0x00, 0x00, 0x00, 0x00,
      // WAVE标志
      0x57, 0x41, 0x56, 0x45,
      // fmt子块
      0x66, 0x6D, 0x74, 0x20,
      // fmt子块长度
      0x10, 0x00, 0x00, 0x00,
      // 音频格式(PCM=1)
      0x01, 0x00,
      // 声道数
      channels, 0x00,
      // 采样率
      sampleRate >> 0, sampleRate >> 8, sampleRate >> 16, sampleRate >> 24,
      // 字节率
      byteRate >> 0, byteRate >> 8, byteRate >> 16, byteRate >> 24,
      // 块对齐
      blockAlign, 0x00,
      // 位深度
      bitDepth, 0x00,
      // data子块
      0x64, 0x61, 0x74, 0x61,
      // data子块长度(暂填0)
      0x00, 0x00, 0x00, 0x00,
    ];
    return header;
  }
}

方案二:发送端编码PCM为MP3后传输

在发送端将原始PCM编码为MP3格式,这样播放端可以直接用audio/mpeg的contentType解析:

  1. 引入音频编码库(如flutter_ffmpeg)
  2. 将mic_stream的PCM流编码为MP3流,再通过Socket发送
  3. 播放端需将Socket接收的所有数据合并为连续流后传入播放器,避免频繁重置音频源

关键注意事项

  • 不要在Socket的listen回调中频繁调用setAudioSource,这会打断播放器的正常加载流程,应该将所有Socket数据合并为一个连续流后再传入播放器
  • 确保音频参数匹配:采样率、声道数、位深在发送端和接收端必须一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 18:45:41