NAudio 获取音频样本

Posted

技术标签:

【中文标题】NAudio 获取音频样本【英文标题】:NAudio Get Audio Samples 【发布时间】:2016-06-02 03:14:03 【问题描述】:

我正在使用 NAudio 从当前正在播放的歌曲中获取样本,并在歌曲播放时绘制波形。我正在使用AudioFileReaderToSampleProvider 将所有样本作为float 获取,然后在歌曲播放时将它们绘制成InkCanvas。我的问题是样本似乎与声音不匹配。我还通过在 NAudio 源代码中的 WPF 示例中使用同一首歌曲来验证这一点。在示例中,波形与声音匹配,但在我的应用程序中不匹配。所以我想知道是否有人可以帮助我找出我在做什么(或阅读)错误,或者我的绘图逻辑是否错误。

这是我当前的代码:

public partial class MainWindow : Window, INotifyPropertyChanged

    private ISampleProvider provider;
    private DispatcherTimer timer;
    private AudioFileReader reader;

    private WaveOut waveOut;
    private StylusPointCollection topPoints, bottomPoints;
    private DrawingAttributes attr;

    private double canvasHeight, canvasWidth;
    private int samplesGroupSize;
    private double drawPos = 0;

    private StrokeCollection _WaveformLines;
    public StrokeCollection WaveformLines
    
        get  return _WaveformLines;  
        set
        
            _WaveformLines = value;
            OnPropertyChanged("WaveformLines");
        
    

    public MainWindow()
    
        InitializeComponent();
        this.DataContext = this;
    

    private void Window_Loaded(object sender, RoutedEventArgs e)
    
        reader = new AudioFileReader("C:\\Users\\Agustin\\Desktop\\DragonRider.mp3");
        waveOut = new WaveOut();
        waveOut.Init(reader);

        provider = reader.ToSampleProvider(); //Here I get the samples
        reader.Position = 0; //Go to the position 0 after reading the samples

        canvasHeight = Waveform.ActualHeight;
        canvasWidth = Waveform.ActualWidth;

        WaveformLines = new StrokeCollection();
        topPoints = new StylusPointCollection();
        topPoints.Add(new StylusPoint(0, (canvasHeight / 2)));
        topPoints.Changed += topPoints_Changed;
        bottomPoints = new StylusPointCollection();
        bottomPoints.Add(new StylusPoint(0, (canvasHeight / 2)));
        bottomPoints.Changed += topPoints_Changed;
        WaveformLines.Add(new Stroke(topPoints));
        WaveformLines.Add(new Stroke(bottomPoints));

        attr = new DrawingAttributes();
        attr.Color = Colors.Green;
        attr.Width = 1.5;
        attr.Height = 1;

        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(1);
        timer.Tick += timer_Tick;
        timer.Start();
        samplesGroupSize = (int)(timer.Interval.TotalSeconds * reader.WaveFormat.SampleRate); //The value for this is 44.
    

    private void PlayButton_Click(object sender, RoutedEventArgs e)
    
        waveOut.Play();
    
    private void PauseButton_Click(object sender, RoutedEventArgs e)
    
        waveOut.Pause();
    

    private void timer_Tick(object sender, EventArgs e)
    
        if (waveOut.PlaybackState == PlaybackState.Playing)
        
            TimeLabel.Content = string.Format("Time: 0", reader.CurrentTime.ToString(@"mm\:ss\:ff")); //NEED TO KEEP WORKING
            float[] samps = new float[samplesGroupSize];
            provider.Read(samps, 0, samps.Length);
            float max = Max(samps);
            float min = Min(samps);
            topPoints.Add(new StylusPoint(drawPos, (canvasHeight / 2) - ((canvasHeight / 2) * max)));
            bottomPoints.Add(new StylusPoint(drawPos, (canvasHeight / 2) - ((canvasHeight / 2) * min)));
            drawPos += 2;
            if (drawPos > canvasWidth)
            
                WaveformLines.Clear();
                topPoints = new StylusPointCollection();
                topPoints.Add(new StylusPoint(0, (canvasHeight / 2)));
                bottomPoints = new StylusPointCollection();
                bottomPoints.Add(new StylusPoint(0, (canvasHeight / 2)));
                WaveformLines.Add(new Stroke(topPoints));
                WaveformLines.Add(new Stroke(bottomPoints));
                drawPos = 0;
            
        
    

    private float Min(float[] samps)
    
        float max = samps[0];
        foreach (float s in samps)
        
            if (s > max)
                max = s;
        
        return max;
    
    private float Max(float[] samps)
    
        float min = samps[0];
        foreach (float s in samps)
        
            if (s < min)
                min = s;
        
        return min;
    

    //I excluded the INotifyPropertyChanged implementation, but in the
    //actual code is located here

我知道这个绘图算法不是很好,但我尝试过其他人,他们似乎也没有跟随音频。

谢谢。

注意:我知道有类似的问题,但其他问题建议使用我已经在使用的 AudioFileReaderToSampleProvider 之类的东西。我的错误可能更多地在于我如何读取样本,可能我丢失了一些字节或必须跳过一些字节,或者可能是一些我没有设置的丢失属性。

【问题讨论】:

您应该认真考虑使用来自WFP example 的代码来处理读取/播放和最小/最大计算工作。这将需要你一点点努力,但我保证这是值得的。然后,您将使用您知道格式正确的数据集,您可以专注于绘图部分。仔细查看AudioPlayback.cs 及其与SampleAggregator.cs 的关系 我很欣赏这个提示,如果这似乎是我唯一的选择,我可能最终会使用一些源代码,但我正在尝试更多地了解音频编程,所以我实际上是尝试不使用源代码并自己做(实际上通过查看源代码我有点打破了我的程序员自我,但这次我可以忍受)。 我还应该提到,如果您正确使用该示例,您将不必尝试使用DispatchTimer 来推动波形图的更新。我想你会发现当你自动从SampleAggregator 接收到你的事件委托方法OnMaximumCalculated 的回调,每 100 毫秒一次(你可以设置一个属性来改变这个间隔,如果你需要),这样做会容易得多。跨度> 好的,谢谢你的提示。我将在 SampleAggregator 中查看更多细节,看看它是如何工作的。 我将您的非可视转换代码放入测试存根中,floats 看起来不错。 【参考方案1】:

您应该认真考虑使用WFP example 中处理读取/播放和最小/最大计算工作的部分代码。

这需要你付出一点努力,但我保证这是值得的。

然后,您将使用您知道格式正确的数据集,并且您可以专注于绘图部分。仔细查看AudioPlayback.cs 及其与SampleAggregator.cs 的关系。

您还会发现,在读取(和播放)样本时获取自动回调是一种比尝试使用DispatchTimer 更好的刷新波形图的方法。它还可以让你不再重新读取波形缓冲区——如果可以的话,你真的想避免这种情况。

编辑:

我测试了您的转换代码,生成的 float 值似乎是正确的(在 -1 到 1 的范围内)。所以我认为问题在于您在 WPF 中绘制波形的方式。

【讨论】:

以上是关于NAudio 获取音频样本的主要内容,如果未能解决你的问题,请参考以下文章

如何从音频文件中获取样本的浮点数组

NAudio : 保留最后一个 ex: 5s 录制的音频并随时保存

使用 NAudio 获取音频的峰值

无法使用 NAudio 获取音频的波形图像

我可以同时从音频流缓冲区写入和播放(在 NAudio 中)吗?

使用 naudio 获取 1 秒音频文件的分贝数