Flutter中从BLE字节流构建WAV文件的问题及Header排查
问题描述
我从BLE设备接收PCM格式的音频字节数据,设备录制5秒音频后发送到安卓手机,参数如下:
- 采样率:8kHz
- 通道数:4
- 采样位深:16位(每采样2字节)
- 总采样数:160000
参考WAV格式规范构建WAV头,但播放时出错。字节流规则:
- 忽略前3字节
- 第3-10字节为数据大小
- 后续数据排列:
- 通道1第1个采样点:第0、1字节(0字节为LSB,1字节为MSB)
- 通道2第1个采样点:第2、3字节(0字节为LSB,1字节为MSB)
- ...以此类推
我的疑问:
- 以下构建的WAV头是否正确?
- 如何在Dart中实现上述字节流处理逻辑?
我构建的WAV头(十六进制,小端序,字符串除外)
52 49 46 46 “RIFF” 24 E2 04 00 FILE SIZE = 320044 - 8 = 320036 57 41 56 45 “WAVE” 66 6d 74 20 fmt 10 00 00 00 size of fmt subchunk = 16 01 00 pcm = 1 04 00 channels = 4 40 1F 00 00 sample rate = 8000 00 FA 00 00 byteRate = 64000 08 00 blockAlign = 8 10 00 bits per sample = 16 64 61 74 61 “data” 00 E2 04 00 size of data = 320000
转换为Uint8List的Dart代码
final header = Uint8List.fromList([ 82, 73, 70, 70, 36, 226, 4, 0, 87, 65, 86, 69, 102, 109, 116, 32, 1, 0, 0, 0, 0, 1, 0, 4, 64, 31, 0, 0, 0, 250, 0, 0, 8, 0, 1, 0, 100, 97, 116, 97, 0, 226, 4, 0, ]);
播放错误日志
I/ExoPlayerImpl(19598): Init 5dd78f5 [ExoPlayerLib/2.17.1] [raven, Pixel 6 Pro, Google, 33] W/io.platform.dev(19598): Accessing hidden method Landroid/media/AudioTrack;->getLatency()I (unsupported, reflection, allowed) E/LoadTask(19598): Unexpected exception loading stream E/LoadTask(19598): java.lang.IllegalStateException E/LoadTask(19598): at com.google.android.exoplayer2.util.Assertions.checkState(Assertions.java:84) E/LoadTask(19598): at com.google.android.exoplayer2.extractor.wav.WavHeaderReader.readFormat(WavHeaderReader.java:100) E/LoadTask(19598): at com.google.android.exoplayer2.extractor.wav.WavExtractor.readFormat(WavExtractor.java:175) E/LoadTask(19598): at com.google.android.exoplayer2.extractor.wav.WavExtractor.read(WavExtractor.java:134) E/LoadTask(19598): at com.google.android.exoplayer2.source.BundledExtractorsAdapter.read(BundledExtractorsAdapter.java:127) E/LoadTask(19598): at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1042) E/LoadTask(19598): at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:412) E/LoadTask(19598): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137) E/LoadTask(19598): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637) E/LoadTask(19598): at java.lang.Thread.run(Thread.java:1012) E/ExoPlayerImplInternal(19598): Playback error E/ExoPlayerImplInternal(19598): com.google.android.exoplayer2.ExoPlaybackException: Source error
问题解决
1. WAV头的错误分析
你构建的WAV头存在参数顺序混乱的问题:fmt子块的标识之后,应该先放置子块大小(16),再依次是音频格式、通道数、采样率等参数,但你的代码打乱了这个顺序,导致ExoPlayer无法正确解析音频格式,触发异常。
2. 正确的Dart WAV头代码
按照标准WAV结构重新构建的头:
final correctHeader = Uint8List.fromList([ // RIFF块 82, 73, 70, 70, // "RIFF"标识 36, 226, 4, 0, // 文件总大小:320044-8=320036(小端序) // WAVE块 87, 65, 86, 69, // "WAVE"标识 // fmt子块 102, 109, 116, 32, // "fmt "标识 16, 0, 0, 0, // fmt子块大小(固定16) 1, 0, // PCM音频格式(值为1) 4, 0, // 4通道 64, 31, 0, 0, // 8000采样率(小端序:0x1F40=8000) 0, 250, 0, 0, // 字节率:8000*4*16/8=64000(小端序:0xFA00=64000) 8, 0, // 块对齐:4*16/8=8 16, 0, // 16位采样位深 // data子块 100, 97, 116, 97, // "data"标识 0, 226, 4, 0, // 数据大小:160000*2=320000(小端序:0x4E200=320000) ]);
3. Dart中处理BLE字节流的逻辑
直接按规则过滤原始数据,再拼接正确的WAV头即可(你的PCM字节顺序符合WAV小端要求,无需额外调整):
Uint8List processBLEAudioData(Uint8List rawData) { // 1. 跳过前3字节 if (rawData.length < 3) throw ArgumentError("原始数据长度不足"); Uint8List pcmData = rawData.sublist(3); // 2. 验证PCM数据长度(确保接收完整) if (pcmData.length != 320000) { throw ArgumentError("PCM数据长度错误:预期320000字节,实际${pcmData.length}字节"); } // 3. 拼接WAV头和PCM数据 Uint8List wavFile = Uint8List(correctHeader.length + pcmData.length); wavFile.setAll(0, correctHeader); wavFile.setAll(correctHeader.length, pcmData); return wavFile; }
内容的提问来源于stack exchange,提问作者chessasuke
相关产品推荐
相关产品推荐

