如何从flutter中的字节流正确构造wav文件?

问题描述 投票:0回答:1

我正在从 BLE 设备接收字节。设备正在录制 5 秒的音频,并将其以 pcm 格式发送到 Android 手机。 8khz,4 通道,16 位采样。我是音频编程的完全初学者,因此尝试构建 wav 标头,我正在关注此链接

来自字节流:

  • 第3-10个字节是要接收的数据大小(忽略前3个字节)
  • 接下来的字节是以下形式的数据:

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:源错误

flutter dart audio wav bytestream
1个回答
0
投票

查看对该问题的评论。您手工制作的标题中有几个拼写错误。通常最好在代码中生成标头部分。

这是一个抽象类,您可以使用具体类对其进行扩展,该具体类完成设置基类的适当值的大部分工作。 (由于历史原因,我一直使用“样本”的数量作为驱动程序来计算其他值(包括长度),但可以随意修改,例如,减少字节数。)包括一个示例具体类,它似乎给出了正确的值,但未经测试。

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,
        );
}
© www.soinside.com 2019 - 2024. All rights reserved.