如何将一个音频文件添加到另一个音频文件?

时间:2012-11-25 00:40:34

标签: iphone ios audio merge record

我有音乐的音频文件,我需要录制歌曲的小片段并在不同时间添加到音乐文件中。你可以通过这种方式理解我有一张长条纸,我必须在较大的纸张上粘贴不同位置的小纸。 请提出一些建议。

让我在这里详细说明。假设我有10个小的声音片段,每个5秒,我有50秒的音乐文件。总共我有11个声音文件。现在我必须通过在音乐文件上不同时间之后添加小10个剪辑来创建一个最终音频文件。像第一个文件应该在5.22秒添加,第二个文件应该在10.34秒添加。

1 个答案:

答案 0 :(得分:2)

我会根据你在评论中的澄清回答:

  

假设我有两个mp3文件,a.mp3为5秒,b.mp3为7   秒我想混合它们以生成持续时间的c.mp3   7秒。

正如评论中所述,我无法向您提供任何iOS细节,但无论使用何种平台和库,我都可以让您了解逻辑上执行此过程的内容。我将使用简单的C ++代码片段来演示。然而,听起来你想要做的就是将a.mp3(以下称A)混合到某个地方b.mp3(以下称B) - 让我们说把A混合到B--的开头来产生合成音频剪辑C.

最重要的是,由于您提到它们是MP3文件而不是WAV或其他一些未压缩的PCM格式(如RAW或AIFF),您首先需要将A和B转换为未压缩的形式,例如{{1} PCM(CD音频格式 - 带符号的16位整数样本,小端),这意味着您将使用一组样本值 - 左右声道交错,如果是立体声音频 - 用于A和B,因此C,当你进行混音时,最后可以选择将其重新编码为MP3。

您应该使用库来处理文件编码/格式问题,但在使用它们时,它们 - 包括用于直接录制或播放的系统接口 - 产生(即,在阅读时)或期望(即,当写入时)基本上相同的基本未压缩PCM样本流格式。对于一般开发,无处不在的S16_LE C库对于约47种文件格式(包括Ogg Vorbis和FLAC(但没有直接的MP3支持))除了WAV格式变体之外,还可用于处理所有这些你可能应该关注它。

为简单起见,我们只考虑单声道声音片段A和B(即,它只是A和B的直接样本值阵列,我们不必担心交错左/右声道);你可以通过独立考虑每个立体声声道(A.left混合B.left,A.right与B.right混合)来轻松地将概念扩展到立体声。如果您的特定A和B是立体声但C不需要,您也可以根据应用将事先将两个输入音频片段转换为单声道。

此外,通常更容易将音频样本用作浮点值,因此转换(或通常,您的音频文件库为您执行此操作 - libsndfile)将未压缩的样本格式转换为浮动 - [-1.0,+ 1.0]范围内的点,其中绝对值1.0表示最大可能的样本值,0.0表示静默。这些样本值包括随时间(即在阵列上)随机音频波形的演变。

首先,您需要确保您有足够的空间" (混合之前防止输出中的削波)。为什么?混合采用信号叠加(加法)原理来组合信号/声音:我们将为每个重叠样本添加A和B,因此混合输出样本可以" clip"如果相应的样品' A和B之和超过1.0或低于-1.0。

有几种方法可以防止剪裁,具体取决于您各自的输入电平以及是否要保持其音量比或者只是将它们平均组合(或者您是否正在使用立体声并且想要使用哪个是A或B最响亮的通道作为你的参考点 - 这是我们听到立体声的最后一个通道。

我们采取最简单的路线,将A和B的音量标准化为峰值,不超过满量程的一半(0.5),这样当它们加在一起时,它们就会#39 ; ll永远不会剪辑(即,没有混合输出样本将超过范围[-1.0,+ 1.0])。如果有3个输入音频片段X,Y和Z与这个方法同时混合在一起,而不是2个输入,我们将每个校准为峰值(0.33)的满量程的1/3。

通过迭代各自的样本缓冲区/数组并确定每个样本缓冲区/数组中的最大样本值,找到A和B libsndfileA_peak的峰值。 [代码如下。]

分别为每个样本缓冲区A和B确定缩放值B_peakA_scale,使得它们相对于相应峰值的乘法产生半尺度。 [代码如下。]

B_scale

埃尔戈:

A_scale * A_peak == 0.5
B_scale * B_peak == 0.5

现在,我们可以将整个样本缓冲区A和B分别乘以A_scale = 1 / (2 * A_peak) B_scale = 1 / (2 * B_peak) A_scale,并将它们标准化为峰值,每个正好是一半,并且没有来自两者的混合样本永远超过全面。也就是说,即使A和B的最大值对于样本对齐,它们的缩放和求和混合输出也将精确地为1.0并且永远不会更大。这种缩放系数通常被称为"增益。"

同样,在混合时有两种方法可以将两个或多个样本缓冲区(音频剪辑)之间的增益标准化,但这是最简单和最通用的演示。此外,它很容易适应将N个不同的音频剪辑混合在一起(如上所述),并且稍微简化,实时混合样本(整个音频剪辑的位置)样本缓冲区不可用,样本处理以块的形式完成,录制时通常就是如此。

现在,我们可以开始混音了。

在这种情况下,A(5秒)适合B(7秒),因此我们可以将混合物直接输出到B中,但为了一般性,让我们将混音输出到单独的样本缓冲区C( 7sec),保持输入A和B不变为浮点样本缓冲区(可能需要重用)。

B_scale为样本计数中A的长度(通常可以确定 - 库会在您加载文件时告诉您,但从根本上它只取决于持续时间和采样率) ,同样适用于A_len和B,输出C B_len,因为问题陈述中有C_len == B_len

分配C,我们的混音输出:

B_len > A_len

在A和B中找到样本绝对值的峰值:

unsigned int C_len = max(A_len, B_len);
double C[] = new double[ C_len ];

找到A和B的半尺度归一化增益:

double A_peak = -1.0, B_peak = -1.0;

for (unsigned int i = 0; i < A_len; ++i) A_peak = max( A_peak, fabs(A[i]) );
for (unsigned int i = 0; i < B_len; ++i) B_peak = max( B_peak, fabs(B[i]) );

将A与B混合成C:

double A_scale = 1 / ( 2 * A_peak );
double B_scale = 1 / ( 2 * B_peak );

请注意,混合和规范化后,浮点缓冲区A和B仍然保持不变。

A可以被认为是零/无声的地方,它没有混入。

如果我们想要在B内的任意偏移处开始混合A(而不是在开始处,假设在这里),那么我们只需计算与我们的时间偏移相对应的样本数({{1}以秒为单位,在整数样本中assert(A_len <= B_len); assert(B_len == C_len); unsigned int x = 0; for (; x < A_len; ++x) C[x] = A_scale * A[x] + B_scale * B[x]; // actual mixing of A and B, finally for (; x < B_len; ++x) C[x] = B_scale * B[x]; // as if A[x] were zero & no abrupt gain change ,并在上述循环结构中的t_offset处的混合中开始包含A. [假设s_offset = t * sample_rate防止溢出。]

鼓励人们尝试更多特定于应用程序的混合输入规范化方法,因为有很多可能性。例如,如果我计算A和B的样本总和的峰值而不是独立地计算每个峰值(主要是先混合并在之后进行校正),该怎么办?什么时候这种[更好]技术不可能?

最后,无论何时混合信号,在混合开始和结束的过渡点(例如,点击)处总是存在伪影的可能性(例如,在A结束但B继续进入C的点)。这是一个相对较低的风险。然而,这种伪像的一般解决方案是对混合进入/离开输入进行短时间淡入和淡出,这通过平滑混合波形消除了伪像,并且可以快速完成以至于听不到