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.
核心问题
- 格式不匹配:
mic_stream获取的是原始PCM音频数据(无容器或编码格式),但你在MyCustomSource中声明contentType: 'audio/mpeg',ExoPlayer会尝试用MP3解析器读取数据,自然无法识别原始PCM。 - 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可以识别这种格式,步骤如下:
接收端拼接PCM流并添加WAV头部:
- 创建
StreamController收集Socket传来的所有PCM数据,合并为连续流 - 给合并后的PCM流添加WAV头部(需匹配采样率、声道数、位深等参数,
mic_stream默认是16位单声道) - 修改
MyCustomSource的contentType为'audio/wav'
- 创建
修改后的播放端逻辑:
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解析:
- 引入音频编码库(如
flutter_ffmpeg) - 将
mic_stream的PCM流编码为MP3流,再通过Socket发送 - 播放端需将Socket接收的所有数据合并为连续流后传入播放器,避免频繁重置音频源
关键注意事项
- 不要在Socket的
listen回调中频繁调用setAudioSource,这会打断播放器的正常加载流程,应该将所有Socket数据合并为一个连续流后再传入播放器 - 确保音频参数匹配:采样率、声道数、位深在发送端和接收端必须一致
内容的提问来源于stack exchange,提问作者zh24
相关产品推荐
相关产品推荐

