将 wav 文件与 libsndfile 混合时的工件
Posted
技术标签:
【中文标题】将 wav 文件与 libsndfile 混合时的工件【英文标题】:artifacts when mixing wav files with libsndfile 【发布时间】:2012-11-18 03:59:19 【问题描述】:我正在尝试实现一个混合钢琴样本的软件。我想创建一个 wav 文件,其中包含一种声音、另一种声音以及两者的混合。
我在一秒钟内截断样本,所以我有以下内容: [一秒声音1][一秒声音2][一秒声音1+声音2]
问题在于每次转换时都会出现奇怪的声音伪影。有人知道它来自哪里吗?
提前致谢。
这是我正在使用的代码:
#include "stdafx.h"
#include <cstdlib>
#include <sndfile.h>
int _tmain(int argc, _TCHAR* argv[])
SF_INFO sInfo1;
SF_INFO sInfo2;
SF_INFO sInfo3;
SNDFILE *sFile1 = NULL;
SNDFILE *sFile2 = NULL;
SNDFILE *sFile3 = NULL;
double *buff1;
double *buff2;
double *buff3;
sf_count_t count1 = 0;
sf_count_t count2 = 0;
sf_count_t count3 = 0;
buff1 = (double*)malloc(88200*sizeof(double));
buff2 = (double*)malloc(88200*sizeof(double));
buff3 = (double*)malloc(88200*sizeof(double));
sInfo1.format = 0;
sInfo2.format = 0;
sFile1 = sf_open("C:/samples/mezzo forte/mcg_mf_022.wav", SFM_READ, &sInfo1);
sFile2 = sf_open("C:/samples/mezzo forte/mcg_mf_046.wav", SFM_READ, &sInfo2);
sInfo3 = sInfo2;
sFile3 = sf_open("C:/samples/test1.wav", SFM_WRITE, &sInfo3);
count1 = sf_read_double(sFile1, buff1, 88200);
count2 = sf_read_double(sFile2, buff2, 88200);
for(int i=0; i<88200; i++)
buff3[i] = buff1[i] + buff2[i] - ( buff1[i] * buff2[i] );
count1 = sf_write_double(sFile3, buff1, 88200);
count2 = sf_write_double(sFile3, buff2, 88200);
count3 = sf_write_double(sFile3, buff3, 88200);
sf_close(sFile1);
sf_close(sFile2);
sf_close(sFile3);
free(buff1);
free(buff2);
free(buff3);
//getchar();
return 0;
【问题讨论】:
【参考方案1】:这不是 libsndfile 问题。这是一个一般的音频合成问题。
每当您将样本截断为任意值(例如 1 秒)时,您都可以听到(或看到,如果您将生成的文件加载到 Audacity 中,并在以下位置检查频谱图和波形)过渡边界)一个工件。这是因为样本波形的突然变化。我将跳过讨论带宽限制问题的尝试,只是敦促您快速淡出样本,而不仅仅是截断它们。这会迫使您的音频波形在过渡之前 [迅速] 接近零 - 平滑。
您可能会发现您还需要淡入(或交叉淡入,如果您与平滑过渡重叠)下一个样本,方法是将其前几个样本加权接近零的值,然后将其提升 [快速,否则你会错过全面的攻击]。首先,在每次转换之前先快速淡出,并且只有在需要时才担心淡入。实现是相同的(一个递增或递减的样本缩放值),但最麻烦的是在 1 秒时任意截断(结束)样本。
您需要使用几个不同的参数来查看哪些参数有效。例如,为了简单起见,您可能希望从线性下降而不是指数或抛物线衰减函数开始。在任何情况下,您都必须决定从转换点开始需要多少个样本(或多少毫秒)才能开始按比例缩小样本值。
编辑:
我最初认为您的混音很好,因为您只询问了过渡工件。我的回答解决了这个问题。然而,值得注意的是,鉴于您的既定目标,我不知道您为什么要完全按照您的原样混入buff3
。如果我正确理解您想简单地将两个声音组合成buff3
,只需将另外两个相应的样本加在一起并确保它们不会剪辑(即超出范围 [-1.0, +1.0])。 libsndfile 会自动“防止”削波,但它只能设置,例如,高于 1.0 到 1.0 的样本值——不能确保两个音频波形混合均匀。
如果您的两个输入声音中的任何一个的电平足够高,那么简单的加法混音就会剪辑,这将是另一种类型的“伪影”(除了可能破坏整个声音,因此在这种特殊情况下,您可能会注意到很多)。但是,对于一般混合,您的循环将是:
for(int i=0; i<88200; i++)
/* multiply sum of signals by factor slightly
less than reciprocal of their count to guard
also against floating-point error. */
buff3[i] = (buff1[i] + buff2[i]) * 0.499;
【讨论】:
非常感谢您的回答。我会尝试淡出选项并随时通知您。至于混合算法,我是从vttoth.com/CMS/index.php/technical-notes/68 拿来的,我觉得很有道理。 您的淡出想法完美运行!我在最后 10 个样本值上使用了线性淡出。再次感谢。 您的链接中发布的算法是错误的。会产生失真。有关更多信息,您可能需要查找环调制:en.wikipedia.org/wiki/Ring_modulation【参考方案2】:我完全不清楚你在试图做什么。您的描述说:“我想创建一个 wav 文件,其中包含一种声音、另一种声音以及两者的混合。”如果你想把一种声音和另一种声音结合起来,为什么还要混合这两种声音呢?这就像把牛奶和奶油的混合物加入一些half and half。
也许您试图使输出为三秒长,第一秒包含第一个声音,第二秒包含第二个声音,第三秒包含两者的混合,但您的代码与那个,所以我将忽略这种可能性。
让我们做一些基础知识。
要复制第一个文件的第一秒,您的循环将如下所示:
buff3[i] = buff1[i] ;
要复制第二个文件的第一秒,您的循环将如下所示:
buff3[i] = buff2[i] ;
要混合两者,您只需将它们相加即可。混合与组合相同。有时我们说我们正在对两个信号进行“叠加”:
buff3[i] = buff1[i] + buff2[i] ;
您通常希望除以 2 以防止信号“越界”:
buff3[i] = ( buff1[i] + buff2[i] ) / 2 ;
请注意,我们没有将信号的样本值彼此相乘。像您所做的逐个样本乘法是为非常不寻常的情况保留的,例如 AM 合成。
【讨论】:
虽然我不明白为什么 OP 从他的添加剂混合物中减去两个样本的乘积,但其余代码实际上是有效的,除了缺少技术上可选的剪切保护在混合步骤中。他的描述与他用 libsndfile 编码的内容相匹配,而且他的编码对于他的既定目标来说是完全合理的。他只是忽略了过渡处波形的连续性。以上是关于将 wav 文件与 libsndfile 混合时的工件的主要内容,如果未能解决你的问题,请参考以下文章
在 C++ 中使用 libsndfile 从 WAV 文件中提取原始音频数据
用 Qt 的 QAudioOutput 类播放 WAV 音频文件(使用了libsndfile外部库)