如何使用 NAudio 将声音文件加载到内存中并稍后使用?

Posted

技术标签:

【中文标题】如何使用 NAudio 将声音文件加载到内存中并稍后使用?【英文标题】:How can I load a sound file into memory using NAudio and use it later? 【发布时间】:2018-05-08 19:50:32 【问题描述】:

我每秒播放 5 次相同的声音文件(在它们之间随机选择),而且我总是加载到内存中,因此程序占用了大量内存。如何将声音文件加载到内存中,然后从那里开始?我正在使用 NAudio。当前代码:

var sound = "sounds/test.mp3";
using (var audioFile = new AudioFileReader(sound))
using (var outputDevice = new WaveOutEvent())

    outputDevice.Init(audioFile);
    outputDevice.Play();
    while (outputDevice.PlaybackState == PlaybackState.Playing)
    
        Thread.Sleep(1000);
    
    threadStop();

【问题讨论】:

【参考方案1】:

如果您删除 using 块,则不会释放 audioFileoutputDevice。然后您可以将它们保留在内存中,每次播放音频时都会使用相同的引用。

使用using 块,您会重复实例化其内存可能不会立即释放的 NAudio 对象。

var sound = "sounds/test.mp3";
var audioFile = new AudioFileReader(sound);
var outputDevice = new WaveOutEvent();
outputDevice.Init(audioFile);
outputDevice.Play();
while (outputDevice.PlaybackState == PlaybackState.Playing)

    Thread.Sleep(1000);

threadStop();

【讨论】:

但是这些变量每秒被替换约 5 次,所以整个地方都会出现内存泄漏。 然后为每个唯一的音频文件实例化一组对象。更新您的问题可能有助于澄清您在做什么。您的问题只是使用单个音频文件源,并且您声明它是“相同的声音文件”。 你没看错,我有多个声音文件。它们在按下一个键时播放。如果我理解正确,我应该为每个声音文件实例化一个新的 AudioFileReader 吗?(但只有一次) 这将是我的方法,为每个文件实例化一个新的AudioFileReader【参考方案2】:

我使用article 中的代码解决了整个问题。 它使用MixingSampleProvider。我将声音加载到一个名为:CachedSound 的自定义类中。然后我使用另一个名为AudioPlaybackEngine 的类来播放它们。它处理混音器,我使用CachedSoundSampleProvider 类来读取缓存的声音。

代码如下所示:

class AudioPlaybackEngine : IDisposable

    private readonly IWavePlayer outputDevice;
    private readonly MixingSampleProvider mixer;

    public AudioPlaybackEngine(int sampleRate = 44100, int channelCount = 2)
    
        outputDevice = new WaveOutEvent();
        mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channelCount));
        mixer.ReadFully = true;
        outputDevice.Init(mixer);
        outputDevice.Play();
    

    public void PlaySound(string fileName)
    
        var input = new AudioFileReader(fileName);
        AddMixerInput(new AutoDisposeFileReader(input));
    

    private ISampleProvider ConvertToRightChannelCount(ISampleProvider input)
    
        if (input.WaveFormat.Channels == mixer.WaveFormat.Channels)
        
            return input;
        
        if (input.WaveFormat.Channels == 1 && mixer.WaveFormat.Channels == 2)
        
            return new MonoToStereoSampleProvider(input);
        
        throw new NotImplementedException("Not yet implemented this channel count conversion");
    

    public void PlaySound(CachedSound sound)
    
        AddMixerInput(new CachedSoundSampleProvider(sound));
    

    private void AddMixerInput(ISampleProvider input)
    
        mixer.AddMixerInput(ConvertToRightChannelCount(input));
    

    public void Dispose()
    
        outputDevice.Dispose();
    

    public static readonly AudioPlaybackEngine Instance = new AudioPlaybackEngine(44100, 2);


class CachedSound

    public float[] AudioData  get; private set; 
    public WaveFormat WaveFormat  get; private set; 
    public CachedSound(string audioFileName)
    
        using (var audioFileReader = new AudioFileReader(audioFileName))
        
            // TODO: could add resampling in here if required
            WaveFormat = audioFileReader.WaveFormat;
            var wholeFile = new List<float>((int)(audioFileReader.Length / 4));
            var readBuffer= new float[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels];
            int samplesRead;
            while((samplesRead = audioFileReader.Read(readBuffer,0,readBuffer.Length)) > 0)
            
                wholeFile.AddRange(readBuffer.Take(samplesRead));
            
            AudioData = wholeFile.ToArray();
        
    


class CachedSoundSampleProvider : ISampleProvider

    private readonly CachedSound cachedSound;
    private long position;

    public CachedSoundSampleProvider(CachedSound cachedSound)
    
        this.cachedSound = cachedSound;
    

    public int Read(float[] buffer, int offset, int count)
    
        var availableSamples = cachedSound.AudioData.Length - position;
        var samplesToCopy = Math.Min(availableSamples, count);
        Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy);
        position += samplesToCopy;
        return (int)samplesToCopy;
    

    public WaveFormat WaveFormat  get  return cachedSound.WaveFormat;  


// This class automatically disposes the file reader that it contains.
class AutoDisposeFileReader : ISampleProvider

    private readonly AudioFileReader reader;
    private bool isDisposed;
    public AutoDisposeFileReader(AudioFileReader reader)
    
        this.reader = reader;
        this.WaveFormat = reader.WaveFormat;
    

    public int Read(float[] buffer, int offset, int count)
    
        if (isDisposed)
            return 0;
        int read = reader.Read(buffer, offset, count);
        if (read == 0)
        
            reader.Dispose();
            isDisposed = true;
        
        return read;
    

    public WaveFormat WaveFormat  get; private set; 

【讨论】:

您好,我尝试使用您发布的此解决方案,但我无法通过扬声器听到任何声音,尽管我可以看到绿色条在“音量混合器”中上下移动,但没有显示任何内容在“设置”->“扬声器音量”中。帮忙? 你还能听到什么吗? (Youtube,音乐之类的东西) 是的,我可以,其他所有应用程序都可以,但我的应用程序没有输出真实的声音,尽管它显示在“音量混合器”中。 好的,我终于发现了我的问题,AudioPlaybackEngine 是静态的,它实际上是在另一个线程中实例化的,而不是我调用它的线程。而且我不完全知道为什么,但这阻止了它的工作。它现在可以工作,因为我在与调用 PlaySound() 的线程相同的线程中实例化 AudioPlaybackEngine。

以上是关于如何使用 NAudio 将声音文件加载到内存中并稍后使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何将带有 dll 的应用程序从内存加载到 AppDomain 中并执行它?

NAudio 拆分 wav 文件问题。其他所有音频文件声音失真,而其他音频文件清晰

使用 naudio 动态播放音频文件

如何使用内存流、NAudio 和 LameMP3 将音频 aiff 转换为 MP3

使用 NAudio 使用 asioout 录制时如何使声音更大?

如何使用 libsndfile 打开加载到内存中的声音文件?