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

生来就可爱ヽ(ⅴ<●) 提交于 2020-01-24 15:23:46

问题


I'm using NAudio to convert & trim some audio files, and I'm trying to add a fade-out to the last few seconds of each file.

I have checked this question, this, and this, but all the answers are talking about playing the wav file with fade, while I need to actually write that fade to an output file.

So, is there any way to do this using NAudio? If not, I'm open to other suggestions.


Edit: This is what I've tried:

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();    
}

When I play the audio after applying the fade using Player.Play() -as shown in the method above-, it works perfectly as expected, and I can hear the fade. Now, I would like to export this result to an output WAV file.

I tried doing that by adding the following line:

WaveFileWriter.CreateWaveFile(outputPath, waveSource);

However, the output file doesn't have any fade applied to it. So, what am I missing here?


回答1:


Okay, let's wrap everything up in case someone encounters the same issue in the future:

With the great help of @yms, I managed to write the fade to a file by using:

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

But that caused the wave writer to only write the last two seconds which makes sense, so I tried using the DelayFadeOutSampleProvider class instead of FadeInOutSampleProvider. With that I was able to write the whole file, but it ended up causing the fading to start in a wrong position (it's more obvious when saving though. Not when playing).

I generated a 10 seconds wav file with Audacity, and used the following method for testing:

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));
    }
}

Here's the file, before & after writing the fade-out:

As shown above, the fade-out doesn't start at the right position.

After some investigation in the DelayFadeOutSampleProvider, I found a bug in the Read method, so I modified it like this:

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;
}

And now everything works just fine.

Here's my fork of the whole class if someone is interested, and I already asked the author (@mark-heath) to update the original gist with this fix.




回答2:


You original code was using the original waveSource as input, which is why you had no fade.

The following alternative uses the fadeOut object:

WaveFileWriter.CreateWaveFile16(outputPath, fadeOut);

The signature of CreateWaveFile16 would be:

public static void CreateWaveFile16(string filename, ISampleProvider sourceProvider)

This can be seen in the source code of the class WaveFileWriter.

Another alternative is to use the class SampleToWaveProvider and covert your fadeOut object into an IWaveProvider, and that allows you to use the regular CreateWaveFile method.

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

As you know, in all cases your output file will only contain the last k seconds corresponding to the fadeout, a different class is needed if you want the whole file with a fadeout.



来源:https://stackoverflow.com/questions/45892543/how-to-properly-write-a-fade-out-to-a-wav-file-using-naudio

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!