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