如何使用 NAudio 正确地将淡出写入 WAV 文件?

How to properly write a fade-out to a WAV file using NAudio?

我正在使用 NAudio 转换 & trim 一些音频文件,我正在尝试在每个文件的最后几秒添加淡出效果。

我已经检查过 this question, , and this,但所有答案都在谈论使用淡入淡出播放 wav 文件,而我需要实际将淡入淡出写入输出文件。

那么,有什么方法可以使用 NAudio 做到这一点吗?如果没有,我愿意接受其他建议。


编辑: 这是我试过的:

private void PerformFadeOut(string inputPath, string outputPath)
{
    WaveFileReader waveSource = new WaveFileReader(inputPath);

    ISampleProvider sampleSource = waveSource.ToSampleProvider();

    OffsetSampleProvider fadeOutSource = new OffsetSampleProvider(sampleSource);
    // Assume the length of the audio file is 122 seconds.
    fadeOutSource.SkipOver = TimeSpan.FromSeconds(120);   // Hard-coded values for brevity

    // Two seconds fade
    var fadeOut = new FadeInOutSampleProvider(fadeOutSource);
    fadeOut.BeginFadeOut(2000);

    Player = new WaveOut();

    Player.Init(fadeOut);
    Player.Play();    
}

当我使用 Player.Play() - 应用淡入淡出后播放音频时,如上述方法所示 -,它按预期完美运行,我可以听到褪色。现在,我想将此结果导出到输出 WAV 文件。

我尝试通过添加以下行来做到这一点:

WaveFileWriter.CreateWaveFile(outputPath, waveSource);

但是,输出文件没有应用任何淡入淡出。那么,我在这里缺少什么?

好的,让我们总结一下,以防将来有人遇到同样的问题:

在@yms 的大力帮助下,我设法使用以下方法将淡入淡出写入文件:

WaveFileWriter.CreateWaveFile(outputPath, new SampleToWaveProvider(fadeOut));

但这导致 wave writer 只写最后两秒 这很有意义 ,所以我尝试使用 DelayFadeOutSampleProvider class 而不是 FadeInOutSampleProvider。这样我就可以写入整个文件,但最终导致淡入淡出的位置错误(保存时更明显。播放时不是 ).

我用Audacity生成了一个10秒的wav文件,用下面的方法测试:

private static void PerformFadeOut(string inputPath, string outputPath, bool playNoSave = false)
{
    WaveFileReader waveSource = new WaveFileReader(inputPath);

    ISampleProvider sampleSource = waveSource.ToSampleProvider();

    // Two seconds fade
    var fadeOut = new DelayFadeOutSampleProvider(sampleSource);
    fadeOut.BeginFadeOut(8000, 2000);

    if(playNoSave)
    {
        // Here the fade is played exactly where expected (@00:08)
        var player = new WaveOut();
        player.Init(fadeOut);
        player.Play();
    }
    else
    {
        // But when saving, the fade is applied @00:04!
        WaveFileWriter.CreateWaveFile(outputPath, new SampleToWaveProvider(fadeOut));
    }
}

这是写入淡出之前和之后的文件:

如上所示,淡出没有从正确的位置开始。

DelayFadeOutSampleProvider里查了一下,发现Read方法有bug,所以修改成这样:

public int Read(float[] buffer, int offset, int count)
{
    int sourceSamplesRead = source.Read(buffer, offset, count);

    lock (lockObject)
    {
        if (fadeOutDelaySamples > 0)
        {
            int oldFadeOutDelayPos = fadeOutDelayPosition;
            fadeOutDelayPosition += sourceSamplesRead / WaveFormat.Channels;
            if (fadeOutDelayPosition > fadeOutDelaySamples)
            {
                int normalSamples = (fadeOutDelaySamples - oldFadeOutDelayPos) * WaveFormat.Channels;
                int fadeOutSamples = (fadeOutDelayPosition - fadeOutDelaySamples) * WaveFormat.Channels;
                // apply the fade-out only to the samples after fadeOutDelayPosition
                FadeOut(buffer, offset + normalSamples, fadeOutSamples);

                fadeOutDelaySamples = 0;
                fadeState = FadeState.FadingOut;
                return sourceSamplesRead;
            }
        }
        if (fadeState == FadeState.FadingIn)
        {
            FadeIn(buffer, offset, sourceSamplesRead);
        }
        else if (fadeState == FadeState.FadingOut)
        {
            FadeOut(buffer, offset, sourceSamplesRead);
        }
        else if (fadeState == FadeState.Silence)
        {
            ClearBuffer(buffer, offset, count);
        }
    }
    return sourceSamplesRead;
}

现在一切正常。

Here's my fork 整个 class 如果有人感兴趣,我已经要求作者 (@mark-heath) 使用此修复程序更新原始要点。

您的原始代码使用原始 waveSource 作为输入,这就是您没有淡入淡出的原因。

以下备选方案使用 fadeOut 对象:

WaveFileWriter.CreateWaveFile16(outputPath, fadeOut);

CreateWaveFile16 的签名为:

public static void CreateWaveFile16(string filename, ISampleProvider sourceProvider)

这可以在source code of the class WaveFileWriter.

中看到

另一种选择是使用 class SampleToWaveProvider 并将您的 fadeOut 对象转换为 IWaveProvider,这样您就可以使用常规的 CreateWaveFile方法。

WaveFileWriter.CreateWaveFile(outputPath, new SampleToWaveProvider(fadeOut))

如您所知,在所有情况下,您的输出文件将只包含与淡出相对应的最后 k 秒,如果您想要整个文件 with[=,则需要不同的 class 34=] 淡出。