Naudio是集录音、播放的源码库。https://github.com/naudio/NAudio,这是基于.net的框架。 在这链接的源码里有各种.net 框架的例子,对于基于.net wpf/universal 的程序,还提供了波形图的绘制。
我从没接触过音频类的技术,有个基于wpf 的项目需要快速提供实时绘制录音的图形。
因为源码库提供的wpf例子是基于文件播放的,其还中包括了比较多参数设置,技术上用了很多反射。很不直观 ,又因为对音频api毫无概念,想快速修改为基于实时录音还是需要费点时间的。不过可能需要修改波形图的样式,最后还是稍等研究一下例子,把图形绘制出来了。
先上一下效果,希望可以让你有兴趣读完和推荐。
在这里篇博文中,我将简述一下绘制录音及实时输出波形和回放及输出波形,并且尽量附近上所有代码,以方便需要节省时间的使用。
- 录音
录音主要是需要用到WaveInEvent,WaveInEvent类提供了对音频参数进行设置以及对数据的采集,参数如通道数、采样率、平均数据传输速率(WaveFormat)。还提供了数据回调事件、录音停止回调函数等参数。 其中,DataAvailable为数据回调参数,是在录音时实时将录音数据传递出来,有需要使用录音数据的可以订阅该事件进行接收业务和相关处理。 在实时绘制波形图时, 我们就需要订阅DataAvailable事件以获得波形的数据源。
- 保存录音文件
为了保存录音数据,需要用到WaveFileWriter, 该类是创建相对应格式的音频文件,并提供想对应的写入数据方法、保存方法等。
- 绘制波形图
以下一段是对源码demo的解读,可以绕过,简单来说,可以认为波形图形控件(PolylineWaveFormControl)是已有,我们需要为图形提供数据源,就叫为(waveProvider)波形提供器吧。
参考NAudio源码中的NAudioWpfDemo,绘制波形图的类主要是PolylineWaveFormControl和PolygonWaveFormControl,以PolylineWaveFormControl为例,先不要考虑曲线是怎么画出来(其实也很简单,音频的采样值,采样值到来的顺序构成每个绘制的点数据,比如第一个采样值为500,则点为(0,500),依次类推)。
这个图形控件是可以直接拿来使用的,如果不需要修改波形图的样式。就算为了修改样式,也照着这两个例子的模式写一下就可以了。如果明白了如何在录音或者播放音频时得到采样值并将采样值传给这个类,那么修改起来也是很简单。
PolylineWaveFormControl和PolygonWaveFormControl,以PolylineWaveFormControl都实现了IWaveFormRenderer中的void AddValue(float maxValue, float minValue),这个AddValue函数在PolylineWaveFormVisualization的OnMaxCalculated(float min, float max)被调用,然后实际上又被AudioPlayback的MaximumCalculated调用,AudioPlayback的MaximumCalculated最终是被SampleAggregator中的MaximumCalculated调用。而在SampleAggregator中MaximumCalculated是被read调用的。read是在SampleAggregator对象关联的音频文件play之后被调用的。
如文章一开始所提,这个例子因为各种原因写得太绕了,不直观。从音频数据到图形,其实就是音频数据流到 SampleAggregator(可视为图形的Provider),SampleAggregator到PolylineWaveFormControl,如下:
因对于实时录音,只需要在WaveInEvent的DataAvailable中调用Read(…..)。
按照以上步骤的代码实现如下:
1.初始化录音对象,存储对象及图形提供器
其中WaveBufferProvider和RecorderBufferProvider是我实现的图形提供器。因为不管是实时录音还是回放文件,提供器的实现方式关键代码都是相同。只是read的实现方式稍微有点区别。将两者共同的抽象为基类WaveBufferProvider。这两个类的代码在文章后面全部提供。
2.将录音数据源传给提供器
70行调用provider的read方法,如上图所绘制,read将调用MaximumCalculated,在我这个实现的AudioRecorder中,24行为MaximumCalculated赋值如下:
然后在调用AudioRecorder和PolylineWaveFormControl的UI对象里为RenderAction赋值如下:
回放的例子可以结合我的代码解读和直接使用源码NAudioWpfDemo的“AudioPlayback“图形Proivder基本可以这么实现。
整个可运行工程的代码在此https://download.youkuaiyun.com/download/mochounv/12958596。
为方便无法下载源码的读者快速使用,我将整个provider类源码也提供如下:
public abstract class WaveBufferProvider: ISampleProvider
{
// volume
public event EventHandler<MaxSampleEventArgs> MaximumCalculated;
private float maxValue;
private float minValue;
public int NotificationCount { get; set; }
int count;
// FFT
public event EventHandler<FftEventArgs> FftCalculated;
public bool PerformFFT { get; set; }
private readonly Complex[] fftBuffer;
private readonly FftEventArgs fftArgs;
private int fftPos;
private readonly int fftLength;
private readonly int m;
// private readonly ISampleProvider source;
protected readonly int channels=1;
public WaveBufferProvider(WaveFormat waveFormat,int fftLength = 1024)
{
channels = waveFormat.Channels;
m = (int)Math.Log(fftLength, 2.0);
this.fftLength = fftLength;
fftBuffer = new Complex[fftLength];
fftArgs = new FftEventArgs(fftBuffer);
WaveFormat = waveFormat;
}
public void Reset()
{
count = 0;
maxValue = minValue = 0;
}
protected void Add(float value)
{
if (PerformFFT && FftCalculated != null)
{
fftBuffer[fftPos].X = (float)(value * FastFourierTransform.HammingWindow(fftPos, fftLength));
fftBuffer[fftPos].Y = 0;
fftPos++;
if (fftPos >= fftBuffer.Length)
{
fftPos = 0;
// 1024 = 2^10
FastFourierTransform.FFT(true, m, fftBuffer);
FftCalculated(this, fftArgs);
}
}
maxValue = Math.Max(maxValue, value);
minValue = Math.Min(minValue, value);
count++;
if (count >= NotificationCount && NotificationCount > 0)
{
MaximumCalculated?.Invoke(this, new MaxSampleEventArgs(minValue, maxValue));
Reset();
}
}
WaveFormat mWaveFormat;
public WaveFormat WaveFormat
{
get
{
return mWaveFormat;
}
set
{
mWaveFormat = value;
NotificationCount = value.SampleRate / 100;
}
}
virtual public int Read(float[] buffer, int offset, int count)
{
return count;
}
}
public class RecorderBufferProvider: WaveBufferProvider
{
public RecorderBufferProvider(WaveFormat waveFormat, int fftLength = 1024):base(waveFormat,fftLength)
{
}
override public int Read(float[] buffer, int offset, int count)
{
for (int n = 0; n < count; n += channels)
{
Add(buffer[n + offset]);
}
return count;
}
}
public class PlayerBufferProvider: WaveBufferProvider
{
private readonly ISampleProvider source;
public PlayerBufferProvider(ISampleProvider source, int fftLength = 1024) : base(source.WaveFormat, fftLength)
{
this.source = source;
}
override public int Read(float[] buffer, int offset, int count)
{
var samplesRead = source.Read(buffer, offset, count);
for (int n = 0; n < samplesRead; n += channels)
{
Add(buffer[n + offset]);
}
return samplesRead;
}
}
public class MaxSampleEventArgs : EventArgs
{
[DebuggerStepThrough]
public MaxSampleEventArgs(float minValue, float maxValue)
{
MaxSample = maxValue;
MinSample = minValue;
}
public float MaxSample { get; private set; }
public float MinSample { get; private set; }
}
public class FftEventArgs : EventArgs
{
[DebuggerStepThrough]
public FftEventArgs(Complex[] result)
{
Result = result;
}
public Complex[] Result { get; private set; }
}
因为wpf/uwp有基于driectx的绘图API,可以增量的绘制音频图形,效率比较高。对于winform等其它基于gui+的win32程序,就无法这样增量绘图,而是将图形绘制我图片上,通过更新图片来更换数据。
例子在此https://github.com/naudio/NAudio.WaveFormRenderer
参考:
https://blog.youkuaiyun.com/li_shidong/article/details/104797827
此链接揭示了provider中NotificationCount关键参数。