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

Node.js 为什么有时我可以使用NodeJS缓冲区连接音频数据,而有时我不能?,node.js,audio,concatenation,buffer,mp3,Node.js,Audio,Concatenation,Buffer,Mp3,作为我正在从事的一个项目的一部分,需要将多个音频数据连接到一个大型音频文件中。音频文件由四个来源生成,单个文件存储在谷歌云存储桶中。每个文件都是mp3文件,很容易验证每个文件是否正确生成(我可以单独播放它们,在我最喜欢的软件中编辑它们,等等) 为了将音频文件合并在一起,nodejs服务器使用axios POST请求从Google云存储加载文件作为阵列缓冲区。从那里,它使用buffer.From()将每个数组缓冲区放入节点缓冲区,因此现在我们有了一个缓冲区对象数组。然后它使用Buffer.conc

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

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

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

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

|------------|--------|--------|-----------|-----|
| 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.mp3
sound3.mp3
。然后在10秒(至少在firefox中播放此mp3文件)时,播放器立即跳到26秒结束

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

感谢阅读?

潜在问题源 采样率 44.1 kHz通常用于音乐,因为它是用于CD音频的。48kHz通常用于视频,因为它是DVD上使用的。这两种采样率都比语音所需的采样率高得多,所以很可能您的各种文本到语音提供者输出的是不同的内容。22.05 kHz(44.1 kHz的一半)是常见的,11.025 kHz也是常见的

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

使用类似(或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的输出。在我上面的例子中,它表示
立体声

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

ID3标签 文件的开头(ID3v2)和/或结尾(ID3v1)通常都有。不太可能在中途得到这些数据。在连接之前,您需要确保此元数据已全部剥离

MP3钻头储液罐 MP3帧不一定是独立的。如果您有一个恒定的比特率流,编码器可能仍然会使用更少的数据来编码一个帧,而使用更多的数据来编码另一个帧。发生这种情况时,某些帧包含其他帧的数据。这样,可以从额外带宽中获益的帧可以获得额外带宽,同时仍然在恒定比特率内拟合整个流。这是“钻头储层”

如果剪切一个流并在另一个流中拼接,则可以拆分一个帧及其从属帧。这通常会导致音频故障,但也可能导致解码器提前跳转。一些行为恶劣的解码器将完全停止播放。在你的例子中,你没有切割任何东西,所以这可能不是你麻烦的根源。。。但我在这里提到它,因为它肯定与你处理这些流的方式有关

另见:

解决 选择“正常”格式,对不符合要求的文件重新采样并重新编码 如果您的大多数来源都是完全相同的格式,并且只有一个或两个未完成,那么您可以转换不一致的文件。从那里,剥离所有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
]);