我正在从 BLE 设备接收字节。设备正在录制 5 秒的音频,并将其以 pcm 格式发送到 Android 手机。 8khz,4 通道,16 位采样。我是音频编程的完全初学者,因此尝试构建 wav 标头,我正在关注此链接。
来自字节流:
Channel-1 第一个数据点 = 第 0 个和第 1 个位置,其中第 0 个是 LSB,第 1 个是 MSB
通道 2 第一个数据点 = 第 2 个和第 3 个位置,其中第 0 个是 LSB,第 1 个是 MSB
...
我的问题是,我如何在 dart 中表示上述内容,下面的标题可以吗?
标头是十六进制的这些值,除字符串外都是小尾数法
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 = 64 000
08 00 blockAlign = 8
10 00 bits per sample = 16
64 61 74 61 “data”
00 E2 04 00 size of data = 320000
这是将上面的标头放入 Uint8List 中,以从中创建一个文件,并将数据附加为 Int8List
最终标题 = 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, ]);
即使我不修改数据字节,我也希望能够播放具有正确标题的 wav 文件,但不断收到此错误:
I/ExoPlayerImpl(19598): Init 5dd78f5 [ExoPlayerLib/2.17.1] [raven, Pixel 6 Pro, Google, 33] W/io.platform.dev(19598):访问隐藏方法 Landroid/media/AudioTrack;->getLatency()I(不支持、反射、允许) E/LoadTask(19598):加载流时发生意外异常 E / LoadTask(19598):java.lang.IllegalStateException E/LoadTask(19598):位于 com.google.android.exoplayer2.util.Assertions.checkState(Assertions.java:84) E / LoadTask(19598):在com.google.android.exoplayer2.extractor.wav.WavHeaderReader.readFormat(WavHeaderReader.java:100) E/LoadTask(19598):位于 com.google.android.exoplayer2.extractor.wav.WavExtractor.readFormat(WavExtractor.java:175) E/LoadTask(19598):位于 com.google.android.exoplayer2.extractor.wav.WavExtractor.read(WavExtractor.java:134) E/LoadTask(19598):位于 com.google.android.exoplayer2.source.BundledExtractorsAdapter.read(BundledExtractorsAdapter.java:127) E / LoadTask(19598):在com.google.android.exoplayer2.source.ProgressiveMediaPeriod $ExtractingLoadable.load(ProgressiveMediaPeriod.java:1042) E/LoadTask(19598):位于 com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:412) E / LoadTask(19598):在java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137) E / LoadTask(19598):在java.util.concurrent.ThreadPoolExecutor $ Worker.run(ThreadPoolExecutor.java:637) E / LoadTask(19598):在java.lang.Thread.run(Thread.java:1012) E/ExoPlayerImplInternal(19598):播放错误 E/ExoPlayerImplInternal(19598):com.google.android.exoplayer2.ExoPlaybackException:源错误
查看对该问题的评论。您手工制作的标题中有几个拼写错误。通常最好在代码中生成标头部分。
这是一个抽象类,您可以使用具体类对其进行扩展,该具体类完成设置基类的适当值的大部分工作。 (由于历史原因,我一直使用“样本”的数量作为驱动程序来计算其他值(包括长度),但可以随意修改,例如,减少字节数。)包括一个示例具体类,它似乎给出了正确的值,但未经测试。
abstract class WavHeader {
WavHeader(
this.tag,
this.channels,
this.sampleRate,
this.bitsPerSample,
this.blockAlign,
this.samples,
this.length,
);
int get overallLength =>
headerTemplate.length -
8 +
fmtTemplate.length +
factTemplate.length +
dataTemplate.length +
length;
Uint8List get header {
final bb = BytesBuilder(copy: false)
..add(riffHeader)
..add(fmtHeader)
..add(factHeader)
..add(dataHeader);
return bb.toBytes();
}
List<int> get riffHeader {
final list = Uint8List.fromList(headerTemplate);
list.buffer.asByteData().setUint32(4, overallLength, Endian.little);
return list;
}
List<int> get fmtHeader {
final list = Uint8List.fromList(fmtTemplate);
list.buffer.asByteData()
..setUint16(8, tag, Endian.little)
..setUint16(10, channels, Endian.little)
..setUint32(12, sampleRate, Endian.little)
..setUint32(16, channels * sampleRate * bitsPerSample ~/ 8, Endian.little)
..setUint16(20, blockAlign, Endian.little)
..setUint16(22, bitsPerSample, Endian.little);
return list;
}
List<int> get factHeader {
final list = Uint8List.fromList(factTemplate);
list.buffer.asByteData().setUint32(8, samples, Endian.little);
return list;
}
List<int> get dataHeader {
final list = Uint8List.fromList(dataTemplate);
list.buffer.asByteData().setUint32(4, length, Endian.little);
return list;
}
final int tag;
final int channels;
final int sampleRate;
final int bitsPerSample;
final int blockAlign;
final int samples;
final int length;
final headerTemplate = <int>[
0x52, 0x49, 0x46, 0x46, // RIFF
0, 0, 0, 0, // length placeholder
0x57, 0x41, 0x56, 0x45 // WAVE
];
final fmtTemplate = <int>[
0x66, 0x6d, 0x74, 0x20, // fmt<space>
0x10, 0, 0, 0, // length 16
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
final factTemplate = <int>[
// fact, length = 4
0x66, 0x61, 0x63, 0x74, // fact
4, 0, 0, 0, // length 4
0, 0, 0, 0
];
final dataTemplate = <int>[
0x64, 0x61, 0x74, 0x61, //data
0, 0, 0, 0
];
}
class PcmWavHeader extends WavHeader {
PcmWavHeader(int samples, int channels)
: super(
1 /* PCM */,
channels,
8000,
16,
2 * channels,
samples,
channels * samples * 2,
);
}