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

标签 node.js audio concatenation buffer mp3

作为我正在进行的项目的一部分,需要将多段音频数据连接到一个大音频文件中。音频文件由四个来源生成,各个文件存储在 Google Cloud 存储桶中。每个文件都是一个 mp3 文件,并且很容易验证每个单独的文件是否正确生成(单独地,我可以播放它们,在我最喜欢的软件中编辑它们等)。

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

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

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

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

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

作为一个场景:
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后端代码,但是使用的过程和方法如上所述。

谢谢阅读?

最佳答案

问题的潜在根源

采样率

44.1 kHz 通常用于音乐,因为它是用于 CD 音频的。 48 kHz 通常用于视频,因为它是用于 DVD 的。这两种采样率都远高于语音所需的采样率,因此您的各种文本转语音提供商可能会输出不同的内容。 22.05 kHz(44.1 kHz 的一半)很常见,11.025 kHz 也很常见。

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

使用类似 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 是采样率。

channel 数

我希望您的语音 MP3 是单声道的,但检查确定不会有什么坏处。和上面一样,检查 FFmpeg 的输出。在我上面的例子中,它说 stereo .

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

ID3 标签

ID3 metadata 是很常见的。在文件的开头 (ID3v2) 和/或结尾 (ID3v1)。不太期望在中途拥有这些数据。您需要确保在连接之前将这些元数据全部删除。

MP3 位储器

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

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

另见:http://wiki.hydrogenaud.io/index.php?title=Bit_reservoir

解决方案

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

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

要进行转换,我建议将其作为 child process 转移到 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,并生成一个新的流。您可以添加参数以重新采样这些输入,并在需要时修改 channel 数。然后输出一个流。使用 concat filter .

这样,您可以接受任何类型的音频文件,您不必编写代码来破解这些流,并且一旦设置您就不必担心它。

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

关于node.js - 为什么有时可以使用 NodeJS 缓冲区连接音频数据,而有时却不能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60750703/

相关文章:

javascript - 如何在闭包之外调用方法

wordpress - 在Wordpress博客首页中添加播放器,而不仅仅是帖子页面

JavaScript 连接应该计数的数字

python - 如何将字符串转换为整数并将它们相加?

c++ - 在存在声卡的情况下响铃

java - 字符串池 : "Te" +"st" faster than "Test"?

node.js - Socket.io + NodeJS IONIC CORS 问题

node.js - 向 Node.js 中的嵌套接口(interface)添加属性

javascript - 如何将小写规范化器添加到Elasticsearch映射对象?

actionscript-3 - 关于在AS3中获取声音波形