将 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 混合时的工件的主要内容,如果未能解决你的问题,请参考以下文章

Libsndfile - 如何处理额外的块(元数据)

在 C++ 中使用 libsndfile 从 WAV 文件中提取原始音频数据

将 c 样式字符串与 c++ 字符串混合时进行优化

用 Qt 的 QAudioOutput 类播放 WAV 音频文件(使用了libsndfile外部库)

使用 libsndfile 为 MATLAB 编写多声道音频

当类别名称与数据混合时如何提取组类别