为什么有时可以使用NodeJS缓冲区连接音频数据,而有时却不能呢?

Japser36

作为我正在进行的项目的一部分,需要将多个音频数据连接成一个大的音频文件。音频文件是从四个来源生成的,各个文件都存储在Google Cloud存储桶中。每个文件都是mp3文件,可以轻松地验证每个文件是否正确生成(可以单独播放,也可以在自己喜欢的软件中对其进行编辑等)。

为了将音频文件合并在一起,nodejs服务器使用axios POST请求将Google Cloud存储中的文件作为数组缓冲区加载。从那里,它使用将每个数组缓冲区放入节点Buffer中Buffer.from(),所以现在我们有了一个Buffer对象数组。然后,它用于Buffer.concat()将Buffer对象连接到一个大的Buffer中,然后我们将其转换为Base64数据并发送到客户端服务器。

这很酷,但是当连接来自不同来源的音频时会出现问题。我上面提到的4个来源是文本到语音软件平台,例如Google Cloud Voice和Amazon Polly。具体来说,我们有来自Google Cloud Voice,Amazon Polly,IBM Watson和Microsoft Azure文本到语音的文件。基本上只有五个文本到语音的解决方案。同样,所有单个文件都可以工作,但是当通过这种方法将它们串联在一起时,会产生一些有趣的效果。

当声音文件被串联时,似乎取决于它们来自哪个平台,声音数据将被包含或将不包含在最终的声音文件中。以下是根据我的测试得出的“兼容性”表:

|------------|--------|--------|-----------|-----|
| Platform / | Google | Amazon | Microsoft | IBM |
|------------|--------|--------|-----------|-----|
| Google     | Yes    | No     | No        | No  |
|------------|--------|--------|-----------|-----|
| Amazon     |        | No     | No        | Yes |
|------------|--------|--------|-----------|-----|
| Microsoft  |        |        | Yes       | No  |
|------------|--------|--------|-----------|-----|
| IBM        |        |        |           | Yes |
|------------|--------|--------|-----------|-----|

效果如下:当播放较大的输出文件时,它将始终开始播放包含的第一个声音文件。从那里开始,如果下一个声音文件兼容,则可以听到,否则将被完全跳过(没有空声或任何声音)。如果跳过该文件,则该文件的“长度”(例如10s长的音频文件)将包含在生成的输出声音文件的末尾。但是,当我的音频播放器到达播放最后一个“兼容”音频的位置时,它会立即跳到结尾。

作为一个方案:

Input:
sound1.mp3 (3s) -> Google
sound2.mp3 (5s) -> Amazon
sound3.mp3 (7s)-> Google
sound4.mp3 (11s) -> IBM

Output:
output.mp3 (26s) -> first 10s is sound1 and sound3, last 16s is skipped.

在这种情况下,输出声音文件的长度为26秒。在开始的10秒钟内,您会听到sound1.mp3sound3.mp3播放的顺序。然后在10秒(至少在firefox中播放此mp3文件)时,播放器立即跳到26秒结束。

我的问题是:有谁知道为什么有时候我可以以这种方式连接音频数据,而有时候却不能呢?在输出文件的末尾为什么会包含“丢失”的数据呢?如果二进制数据在某些情况下可以工作,那么它应该在所有情况下都不能工作吗,因为所有文件都具有mp3编码?如果我输入错了,请让我知道如何成功连接任何mp3文件:)我可以提供我的nodeJS后端代码,但是上面描述了使用的过程和方法。

谢谢阅读?

布拉德

问题的潜在根源

采样率

CD音频中经常使用44.1 kHz的音乐。视频通常使用48 kHz,因为DVD上已经使用了48 kHz。这两个采样率都远远高于语音所要求的采样率,因此您的各种文本语音转换提供程序可能输出的内容有所不同。通常是22.05 kHz(一半为44.1 kHz),那里也有11.025 kHz。

尽管每个帧都指定了自己的采样率,从而可以生成具有不同采样率的流,但我从未见过解码器尝试在流中切换采样率。我怀疑解码器正在跳过这些帧,或者甚至跳过任意块,直到它再次获得一致的数据。

使用FFmpeg(或FFprobe)之类的东西来确定文件的采样率是多少:

ffmpeg -i sound2.mp3

您将获得如下输出:

Duration: 00:13:50.22, start: 0.011995, bitrate: 192 kb/s
  Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 192 kb/s

在此示例中,44.1 kHz是采样率。

频道数

我希望您的语音MP3可以是单声道,但是检查确定不会有任何伤害。和上面一样,检查FFmpeg的输出。在上面的示例中,它说stereo

与采样率一样,从技术上讲,每个帧都可以指定自己的频道数,但我不知道有哪个播放器会中途切换频道数。因此,如果要进行串联,则需要确保所有通道数都相同。

ID3标签

通常在文件的开头(ID3v2)和/或结尾(ID3v1)ID3元数据人们不太希望将这些数据放在中间。您需要确保在连接之前全部删除了该元数据。

MP3位存储器

MP3帧不一定独立存在。如果您具有恒定的比特率流,则编码器可能仍会使用较少的数据来编码一帧,而使用更多的数据来编码另一帧。发生这种情况时,某些帧会包含其他帧的数据。这样,可以从额外带宽中受益的帧可以获得它,同时仍将整个流以恒定的比特率进行适配。这就是“位库”。

如果剪切流并拼接到另一个流中,则可能会拆分一个帧及其从属帧。这通常会导致音频故障,但也可能导致解码器向前跳过。一些表现不佳的解码器将完全停止播放。在您的示例中,您什么都没有减少,所以这可能不是您麻烦的源头……但是我在这里提到它,因为它与您处理这些流的方式绝对相关。

另请参阅:http : //wiki.hydrogenaud.io/index.php?title=Bit_reservoir

解决方案

选择“正常”格式,重新采样并重新编码不合格的文件

如果大多数来源都是完全相同的格式,并且只有一个或两个未解决的来源,则可以转换不合格的文件。从那里,剥离所有内容的ID3标签并连接起来。

要进行转换,我建议将其作为子进程插入FFmpeg

child_process.spawn('ffmpeg' [
  // Input
  '-i', inputFile, // Use '-' to write to STDIN instead

  // Set sample rate
  '-ar', '44100',

  // Set audio channel count
  '-ac', '1',

  // Audio bitrate... try to match others, but not as critical
  '-b:a', '64k',

  // Ensure we output an MP3
  '-f', 'mp3',

  // Output
  outputFile // As with input, use '-' to write to STDOUT
]);

最佳解决方案:让FFmpeg(或类似产品)为您完成工作

最简单,最可靠的解决方案是让FFmpeg为您构建一个全新的流。这将导致您的音频文件被解码为PCM,并产生新的流。您可以添加参数以对这些输入进行重新采样,并根据需要修改通道数。然后输出一个流。使用concat过滤器

这样,您可以接受任何类型的音频文件,无需编写代码即可将这些流一起黑化,并且一旦安装,您就不必担心。

唯一的缺点是,它将需要对所有内容进行重新编码,这意味着会丢失另一代质量。无论如何,这对于任何不符合要求的文件都是必需的,而这只是语音,因此我不会再考虑了。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

为什么Ajax有时会对序列化的数据进行urlencode,而有时却不会呢?

来自分类Dev

为什么AS有时会接受大位移而有时却不接受?

来自分类Dev

为什么AS有时会接受大位移而有时却不接受?

来自分类Dev

为什么此URLClassLoader有时起作用而有时却不起作用?

来自分类Dev

为什么我有时会出错而有时却没有呢?

来自分类Dev

为什么有时我们快速使用-> void作为完成处理程序,但有时却不使用呢?

来自分类Dev

为什么QEMU的hostfwd选项有时需要root访问,而有时却不需要root访问

来自分类Dev

为什么`:type`有时显示`a`,而有时显示`t`?

来自分类Dev

为什么有时显示背景而有时不显示背景?

来自分类Dev

为什么我的while循环有时会卡住,而有时却不会出现在“秘密圣诞老人”程序中?

来自分类Dev

为什么使用==比较两个Integer有时可行,有时却不可行?

来自分类Dev

为什么Ruby Procs有时会返回而有时却是错误?

来自分类Dev

为什么`.asInstanceOf`有时会抛出,有时却不会抛出?

来自分类Dev

为什么vim有时显示^ M而有时不显示(即使它们在那里)?

来自分类Dev

为什么“尝试/捕获”中的命令有时需要-ErrorAction停止,而有时则不需要?

来自分类Dev

为什么ng-model有时会预先填充而有时却没有?

来自分类Dev

为什么vim有时显示^ M,有时却不显示(即使它们在那里)?

来自分类Dev

为什么gpg --list-keys有时打印子键,有时却不打印子键?

来自分类Dev

为什么别名有时像nameref一样起作用,有时却不起作用?

来自分类Dev

为什么同一条SQL有时会报告错误,而有时却运行良好?

来自分类Dev

为什么BeautifulSoup有时会使用find_all查找所有元素,而有时却找不到?

来自分类Dev

Google图表,有时可以正常运行,有时却不可以?

来自分类Dev

为什么嵌套的子类可以访问其父类的私有成员,但子孙却不能呢?

来自分类Dev

为什么有时滴胶不能运行?

来自分类Dev

C:UART,ISR,循环FIFO缓冲区:有时以错误的顺序发送字节

来自分类Dev

Kubernetes部署有时可以工作,有时却不行

来自分类Dev

在Ubuntu上,为什么有时有时会“ sudo apt-get”而有时却会“ sudo aptitude”?

来自分类Dev

为什么有时我可以使用嵌套模块中的函数而无需导入整个路径?

来自分类Dev

为什么缓冲区大小会影响音频数据?

Related 相关文章

  1. 1

    为什么Ajax有时会对序列化的数据进行urlencode,而有时却不会呢?

  2. 2

    为什么AS有时会接受大位移而有时却不接受?

  3. 3

    为什么AS有时会接受大位移而有时却不接受?

  4. 4

    为什么此URLClassLoader有时起作用而有时却不起作用?

  5. 5

    为什么我有时会出错而有时却没有呢?

  6. 6

    为什么有时我们快速使用-> void作为完成处理程序,但有时却不使用呢?

  7. 7

    为什么QEMU的hostfwd选项有时需要root访问,而有时却不需要root访问

  8. 8

    为什么`:type`有时显示`a`,而有时显示`t`?

  9. 9

    为什么有时显示背景而有时不显示背景?

  10. 10

    为什么我的while循环有时会卡住,而有时却不会出现在“秘密圣诞老人”程序中?

  11. 11

    为什么使用==比较两个Integer有时可行,有时却不可行?

  12. 12

    为什么Ruby Procs有时会返回而有时却是错误?

  13. 13

    为什么`.asInstanceOf`有时会抛出,有时却不会抛出?

  14. 14

    为什么vim有时显示^ M而有时不显示(即使它们在那里)?

  15. 15

    为什么“尝试/捕获”中的命令有时需要-ErrorAction停止,而有时则不需要?

  16. 16

    为什么ng-model有时会预先填充而有时却没有?

  17. 17

    为什么vim有时显示^ M,有时却不显示(即使它们在那里)?

  18. 18

    为什么gpg --list-keys有时打印子键,有时却不打印子键?

  19. 19

    为什么别名有时像nameref一样起作用,有时却不起作用?

  20. 20

    为什么同一条SQL有时会报告错误,而有时却运行良好?

  21. 21

    为什么BeautifulSoup有时会使用find_all查找所有元素,而有时却找不到?

  22. 22

    Google图表,有时可以正常运行,有时却不可以?

  23. 23

    为什么嵌套的子类可以访问其父类的私有成员,但子孙却不能呢?

  24. 24

    为什么有时滴胶不能运行?

  25. 25

    C:UART,ISR,循环FIFO缓冲区:有时以错误的顺序发送字节

  26. 26

    Kubernetes部署有时可以工作,有时却不行

  27. 27

    在Ubuntu上,为什么有时有时会“ sudo apt-get”而有时却会“ sudo aptitude”?

  28. 28

    为什么有时我可以使用嵌套模块中的函数而无需导入整个路径?

  29. 29

    为什么缓冲区大小会影响音频数据?

热门标签

归档