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

uemypmqf  于 2023-11-21  发布在  Flutter
关注(0)|答案(2)|浏览(214)

我正在从BLE设备接收字节。该设备正在录制音频5秒,并以PCM格式发送到Android手机。

  • 8kHz
  • 4个通道
  • 16位样本(每个样本2个字节)。
  • 样本数量= 160 000
  • 5秒录音

我是一个完整的初学者在音频编程,所以试图构建wav头,我下面这个link
从字节流:

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

通道-1第1个数据点=第0个和第1个位置,其中第0个是LSB,第1个是MSB
通道2第1个数据点=第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
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,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/www.example.com(19598):io.platform.dev(unsupported,reflection,allowed)E/LoadTask(19598):加载流时出现意外异常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):网址:www.example.com(WavExtractor.java:134)E/LoadTask(19598):网址:www.example.com(BundledExtractorsAdapter.java:127)E/LoadTask(19598):at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1042)E/LoadTask(19598):在com.google.android.exoplayer2.upstream.Loader $www.example.com(Loader.java:412)E/LoadTask(19598):at java. util. concurrent. ThreadPoolExecutor. runWorker(ThreadPoolExecutor.java:1137)E/LoadTask(19598):at java. util. concurrent. ThreadPoolExecutor $www.example.com(ThreadPoolExecutor.java:637)E/LoadTask(19598):at www.example.com(Thread.java:1012)E/ExoPlayerImplInternal(19598):Playback error E/ExoPlayerImplInternal(19598):com.google.android.exoplayer2.ExoPlaybackException:Source error

zc0qhyus

zc0qhyus1#

请看这个问题的评论。在你手工制作的标题中有几个错别字。通常最好用代码生成标题部分。
下面是一个抽象类,您可以用一个具体类来扩展它,该具体类执行设置基类的适当值的大部分工作。(由于历史原因,我一直使用“样本”的数量作为驱动程序来计算其他值(包括长度),但可以随意修改,比如,)我已经包含了一个示例具体类,它似乎给予正确的值,但没有经过测试。

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,
        );
}

字符串

2ul0zpep

2ul0zpep2#

你不需要手工编写WAV头文件,我为此写了一个包:package:wav。使用构造函数创建一个类似Wav(channels, sampleRate, format)的Wav对象,然后使用write方法将其写入Uint8List
如果你想使用它来生成整个WAV,你需要将PCM数据转换为浮点采样(传递给channels参数)。
或者,您可以通过创建一个虚拟WAV(为通道传递零,与真实的样本数据长度相同)来生成标题,将其写入Uint8List,然后从该列表的开头获取标题。

相关问题