NAudio:正确地使用MixingSampleProvider和VolumeSampleProvider

时间:2022-09-08 22:43:00

I have been using NAudio with the "Fire and Forget Audio Playback with NAudio" tutorial (thank you Mark for this awesome utility!) as written here: http://mark-dot-net.blogspot.nl/2014/02/fire-and-forget-audio-playback-with.html

我一直在使用NAudio与“Fire,忘记音频回放与NAudio”教程(感谢你为这个很棒的实用程序做的标记!),这里写的是:http://mark-dot-net.blogspot.nl/2014/02/fire-and- Audio - Playback -with.html。

I managed to add a VolumeSampleProvider to it, using the MixingSampleProvider as input. However, when I now play two sounds right after each other, the first sound always gets the volume of the second as well, even though the first is already playing.

我设法向它添加了一个VolumeSampleProvider,使用MixingSampleProvider作为输入。然而,当我现在同时演奏两个音时,第一个音也会得到第二个音的音量,尽管第一个音已经开始演奏了。

So my question is: How do I add sounds with an individual volume per sound?

所以我的问题是:我如何将声音添加到每个声音的单个音量中?

This is what I used:

这是我用过的:

        mixer = new MixingSampleProvider(waveformat);
        mixer.ReadFully = true;
        volumeProvider = new VolumeSampleProvider(mixer);
        panProvider = new PanningSampleProvider(volumeProvider);
        outputDevice.Init(panProvider);
        outputDevice.Play();

2 个解决方案

#1


1  

I'm not 100% certain of what you are asking and I don't know if you solved this already but here's my take on this.

我不是百分之百确定你要问什么,我不知道你是否已经解决了这个问题但这是我的看法。

ISampleProvider objects play the "pass the buck" game to their source ISampleProvider via the Read() method. Eventually, someone does some actual reading of audio bytes. Individual ISampleProvider classes do whatever they do to the bytes.

ISampleProvider对象通过Read()方法将“传递buck”游戏传递给它们的源ISampleProvider。最终,有人会做一些音频字节的实际读取。每个ISampleProvider类对字节做任何事情。

MixingSampleProvider, for instance, takes N audio sources... those get mixed. When Read() is called, it iterates the audio sources and reads count bytes from each.

例如,MixingSampleProvider使用N个音频源……那些混。调用Read()时,它迭代音频源并从每个源中读取计数字节。

Passing it to a VolumeSampleProvider handles all the bytes (from those various sources) as a group... it says:

将它传递给VolumeSampleProvider作为一个组来处理所有的字节(来自不同的源)。它说:

buffer[offset+n] *= volume;

That's going to adjust the bytes across the board... so every byte gets adjusted in the buffer by the volume multiplier;

这将调整整个屏幕的字节……所以每个字节在缓冲区中被卷乘子调整;

The PanningSampleProvider just provides a multiplier to the stereo audio and adjusts the bytes accordingly, doing the same sort of thing as the VolumeSampleProvider.

PanningSampleProvider只是为立体声音频提供了一个倍增器,并相应地调整字节,做的事情与VolumeSampleProvider类似。

If you want to individually handle audio source volumes, you need to handle that upstream of the MixingSampleProvider. Essentially, the things that you pass to the MixingSampleProvider need to be able to have their volume adjusted independently.

如果希望单独处理音频源卷,则需要处理MixingSampleProvider的上游。本质上,您传递给MixingSampleProvider的东西需要能够独立地调整它们的卷。

If you passed a bunch of SampleChannel objects to your MixingSampleProvider... you could accomplish independent volume adjustment. The Samplechannel class incorporates a VolumeSampleProvider object and provides a Volume property that allows one to set the volume on that VolumeSampleProvider object.

如果您将一些SampleChannel对象传递给MixingSampleProvider…你可以完成独立的音量调节。Samplechannel类合并了一个VolumeSampleProvider对象,并提供了一个卷属性,该属性允许您在该VolumeSampleProvider对象上设置卷。

SampleChannel also incorporates a MeteringSampleProvider that provides reporting of the maximum sample value during a given period. It raises an event that gives you an array of those values, one per channel.

SampleChannel还包含一个MeteringSampleProvider,它提供给定时间段内最大样本值的报告。它引发一个事件,该事件为您提供这些值的数组,每个通道一个。

#2


0  

I realized (thanks to itsmatt) that the only way to make this work, is to leave the mixer alone and adjust the panning and volume of each CachedSound individually, before adding it to the mixer. Therefore I needed to rewrite the CachedSoundSampleProvider, using a pan and volume as extra input parameters.

我意识到(多亏了它的马特),使这一工作的唯一方法,是离开混音器,调整每一个CachedSound的平移和音量,然后将其添加到混音器中。因此,我需要重写CachedSoundSampleProvider,使用pan和volume作为额外的输入参数。

This is the new constructor:

这是新的构造函数:

    public CachedSoundSampleProvider(CachedSound cachedSound, float volume = 1, float pan = 0)
    {
        this.cachedSound = cachedSound;
        LeftVolume = volume * (0.5f - pan / 2);
        RightVolume = volume * (0.5f + pan / 2);
    }

And this is the new Read() function:

这是新的Read()函数:

    public int Read(float[] buffer, int offset, int count)
    {
        long availableSamples = cachedSound.AudioData.Length - position;
        long samplesToCopy = Math.Min(availableSamples, count);

        int destOffset = offset;
        for (int sourceSample = 0; sourceSample < samplesToCopy; sourceSample += 2)
        {
            float outL = cachedSound.AudioData[position + sourceSample + 0];
            float outR = cachedSound.AudioData[position + sourceSample + 1];

            buffer[destOffset + 0] = outL * LeftVolume;
            buffer[destOffset + 1] = outR * RightVolume;
            destOffset += 2;
        }

        position += samplesToCopy;
        return (int)samplesToCopy;
    }

#1


1  

I'm not 100% certain of what you are asking and I don't know if you solved this already but here's my take on this.

我不是百分之百确定你要问什么,我不知道你是否已经解决了这个问题但这是我的看法。

ISampleProvider objects play the "pass the buck" game to their source ISampleProvider via the Read() method. Eventually, someone does some actual reading of audio bytes. Individual ISampleProvider classes do whatever they do to the bytes.

ISampleProvider对象通过Read()方法将“传递buck”游戏传递给它们的源ISampleProvider。最终,有人会做一些音频字节的实际读取。每个ISampleProvider类对字节做任何事情。

MixingSampleProvider, for instance, takes N audio sources... those get mixed. When Read() is called, it iterates the audio sources and reads count bytes from each.

例如,MixingSampleProvider使用N个音频源……那些混。调用Read()时,它迭代音频源并从每个源中读取计数字节。

Passing it to a VolumeSampleProvider handles all the bytes (from those various sources) as a group... it says:

将它传递给VolumeSampleProvider作为一个组来处理所有的字节(来自不同的源)。它说:

buffer[offset+n] *= volume;

That's going to adjust the bytes across the board... so every byte gets adjusted in the buffer by the volume multiplier;

这将调整整个屏幕的字节……所以每个字节在缓冲区中被卷乘子调整;

The PanningSampleProvider just provides a multiplier to the stereo audio and adjusts the bytes accordingly, doing the same sort of thing as the VolumeSampleProvider.

PanningSampleProvider只是为立体声音频提供了一个倍增器,并相应地调整字节,做的事情与VolumeSampleProvider类似。

If you want to individually handle audio source volumes, you need to handle that upstream of the MixingSampleProvider. Essentially, the things that you pass to the MixingSampleProvider need to be able to have their volume adjusted independently.

如果希望单独处理音频源卷,则需要处理MixingSampleProvider的上游。本质上,您传递给MixingSampleProvider的东西需要能够独立地调整它们的卷。

If you passed a bunch of SampleChannel objects to your MixingSampleProvider... you could accomplish independent volume adjustment. The Samplechannel class incorporates a VolumeSampleProvider object and provides a Volume property that allows one to set the volume on that VolumeSampleProvider object.

如果您将一些SampleChannel对象传递给MixingSampleProvider…你可以完成独立的音量调节。Samplechannel类合并了一个VolumeSampleProvider对象,并提供了一个卷属性,该属性允许您在该VolumeSampleProvider对象上设置卷。

SampleChannel also incorporates a MeteringSampleProvider that provides reporting of the maximum sample value during a given period. It raises an event that gives you an array of those values, one per channel.

SampleChannel还包含一个MeteringSampleProvider,它提供给定时间段内最大样本值的报告。它引发一个事件,该事件为您提供这些值的数组,每个通道一个。

#2


0  

I realized (thanks to itsmatt) that the only way to make this work, is to leave the mixer alone and adjust the panning and volume of each CachedSound individually, before adding it to the mixer. Therefore I needed to rewrite the CachedSoundSampleProvider, using a pan and volume as extra input parameters.

我意识到(多亏了它的马特),使这一工作的唯一方法,是离开混音器,调整每一个CachedSound的平移和音量,然后将其添加到混音器中。因此,我需要重写CachedSoundSampleProvider,使用pan和volume作为额外的输入参数。

This is the new constructor:

这是新的构造函数:

    public CachedSoundSampleProvider(CachedSound cachedSound, float volume = 1, float pan = 0)
    {
        this.cachedSound = cachedSound;
        LeftVolume = volume * (0.5f - pan / 2);
        RightVolume = volume * (0.5f + pan / 2);
    }

And this is the new Read() function:

这是新的Read()函数:

    public int Read(float[] buffer, int offset, int count)
    {
        long availableSamples = cachedSound.AudioData.Length - position;
        long samplesToCopy = Math.Min(availableSamples, count);

        int destOffset = offset;
        for (int sourceSample = 0; sourceSample < samplesToCopy; sourceSample += 2)
        {
            float outL = cachedSound.AudioData[position + sourceSample + 0];
            float outR = cachedSound.AudioData[position + sourceSample + 1];

            buffer[destOffset + 0] = outL * LeftVolume;
            buffer[destOffset + 1] = outR * RightVolume;
            destOffset += 2;
        }

        position += samplesToCopy;
        return (int)samplesToCopy;
    }