1

我有一个音乐的音频文件,我需要录制歌曲的小片段并在不同的时间添加到音乐文件中。你可以这样理解,我有一条长条纸,我必须将小纸条粘贴在大纸条的不同位置。请提出一些方法。

让我在这里提供更多细节。假设我有 10 个 5 秒的小声音片段,我有一个 50 秒的音乐文件。所以我总共有 11 个声音文件。现在,我必须通过在音乐文件的不同时间后添加 10 个小片段来创建一个最终的音频文件。第一个文件应该在 5.22 秒添加,第二个文件应该在 10.34 秒添加。

4

1 回答 1

2

我将根据您在评论中的澄清回答:

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

如评论中所述,我无法为您提供任何 iOS 细节,但我可以让您了解执行此过程所需的逻辑,无论使用何种平台和库。我将使用简单的 C++ 片段来演示。但是,听起来您想要做的是在某处将 a.mp3(以下称为 A)混合到 b.mp3(以下称为 B)中——假设将 A 混合到 B 的开头——以产生最终的音频剪辑 C。

首先,由于您提到它们是 MP3 文件,而不是 WAV 或其他未压缩的 PCM 格式,例如 RAW 或 AIFF,您首先需要将 A 和 B 转换为未压缩的格式,例如S16_LEPCM(CD 音频格式 - 签名 16 -bit integer samples, little endian),这意味着您将使用一组样本值——如果是立体声音频,左右声道交错——用于 A 和 B,因此 C,最后一个可能稍后是完成混音后可选择重新编码为 MP3。

您应该使用一个库来为您处理文件编码/格式问题,但是在使用它们时,它们都——包括用于直接录制或回放的系统接口——产生(即,读取时)或期望(即,当写作)本质上是相同的基本未压缩 PCM 样本流格式。对于一般开发,无处不在的libsndfileC 库可用于为您处理大约 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 加在一起,因此如果来自 A 和 B 的相应样本的总和,则混合输出样本可能会“剪辑”超过 1.0 或低于 -1.0。

有几种方法可以防止削波,具体取决于您各自的输入电平以及您是要保留它们的音量比还是简单地将它们相等地组合(或者您是否正在使用立体声并想要使用 A 中最响亮的通道或 B 作为您的参考点——这是我们最后一次听到立体声)。

我们将采用最简单的方法,将 A 和 B 的音量标准化为不超过满量程一半 (0.5) 的峰值,这样当它们加在一起时,它们永远不会削波(即,永远不会有混合输出样本超出范围 [-1.0, +1.0])。如果不是 2 个输入,而是有 3 个输入音频剪辑 X、Y 和 Z 要使用此方法同时混合在一起,我们将在峰值 (0.33) 处将每个标准化为满量程的 1/3。

通过迭代它们各自的样本缓冲区/数组并确定每个样本缓冲区/数组中的最大样本值,A_peak找到A 和 B 的峰值。B_peak[要遵循的代码。]

分别为每个样本缓冲区 A 和 B确定缩放值A_scaleB_scale以使它们与各自的峰值相乘产生半刻度。[要遵循的代码。]

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

尔格:

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

现在,我们可以将整个样本缓冲区 A 和 B 分别乘以A_scaleB_scale,它们将被归一化为每个正好半刻度的峰值,并且两者的混合样本永远不会超过满量程。也就是说,即使 A 和 B 的最大值与样本对齐,它们的缩放和总和混合输出也将恰好为 1.0,并且永远不会更大。这种比例系数通常被称为“增益”。

同样,有多种方法可以在混合时对两个或多个样本缓冲区(音频剪辑)之间的增益进行归一化,但这是最简单和最通用的演示方法。另外,它很容易适应将 N 个不同的音频剪辑混合在一起(如上所述),并且稍微简化后,可以实时混合样本(其中整个音频剪辑的样本缓冲区不可用并且样本处理已完成)块,就像录制时经常发生的情况一样)。

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

在这种情况下,A(5 秒)适合 B(7 秒),因此我们可以将混合直接输出到 B 中,但为了一般性,让我们将混合输出到单独的样本缓冲区 C(7 秒),留下输入 A B 原样作为浮点样本缓冲区(可能被重用)。

让我们A_len成为样本计数中 A 的长度(这是很容易确定的——当你加载文件时,库会告诉你,尽管从根本上说它只取决于持续时间和采样率),同样对于B_len和 B,以及对于输出 C,C_len == B_len,因为B_len > A_len在您的问题陈述中。

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

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 的半标度归一化增益:

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

将 A 与 B 混合为 C:

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

请注意,浮点缓冲区 A 和 B 在混合和归一化后仍保持不变。

A 在没有混合的任何地方都可以被认为是零/静音。

如果我们想在 B 内的任意偏移处开始混合 A(而不是在此处假设的开始处),那么我们只需计算与我们的时间偏移相对应的样本数(t_offset以秒为单位,s_offset = t * sample_rate以整数样本为单位),并且x == s_offset在上述循环结构中开始在混合中包含 A 。[假设是s_offset + A_len <= C_len为了防止溢出。]

鼓励人们尝试更多特定于应用程序的方法来规范化混合输入,因为有很多可能性。例如,如果我计算了 A 和 B 样本之和的峰值,而不是分别计算每个峰值(基本上是先混合然后校正),该怎么办?这种[更好的]技术何时不可能实现?

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

于 2012-11-28T01:25:34.533 回答