下午写了一篇关于NAudio的录音、播放和波形图的博客,不太满意,感受写的太乱,又总结了下ide
NAudio是个相对成熟、开源的C#音频开发工具,它包含录音、播放录音、格式转换、混音调整等功能。本次介绍主要功能有音频、录音文件播放、实时音频流波形图显示等。具体以下:函数
1. 录音工具
NAudio录音主要使用WaveIn和WaveFileWriter两个类开发工具
1.1 WaveInspa
WaveIn的功能是对录音的音频参数进行设置以及对数据的采集,参数如通道数、采样率、平均数据传输速率(WaveFormat)、数据回调事件、录音中止回调函数等参数 .net
其中,DataAvailable为数据回调参数,是在录音时实时将录音数据传递出来,有须要使用录音数据的能够订阅该事件进行接收业务和相关处理。调试
1.2 WaveFileWritercode
该类是建立相对应格式的音频文件,并提供想对应的写入数据方法、保存方法等,具体以下:orm
public class WaveFileWriter : Stream { public WaveFileWriter(Stream outStream, WaveFormat format); public WaveFileWriter(string filename, WaveFormat format); ~WaveFileWriter(); public override long Position { get; set; } public override bool CanWrite { get; } public override bool CanRead { get; } public WaveFormat WaveFormat { get; } public TimeSpan TotalTime { get; } public override long Length { get; } public string Filename { get; } public override bool CanSeek { get; } public static void CreateWaveFile(string filename, IWaveProvider sourceProvider); public static void CreateWaveFile16(string filename, ISampleProvider sourceProvider); public static void WriteWavFileToStream(Stream outStream, IWaveProvider sourceProvider); public override void Flush(); public override int Read(byte[] buffer, int offset, int count); public override long Seek(long offset, SeekOrigin origin); public override void SetLength(long value); public override void Write(byte[] data, int offset, int count); [Obsolete("Use Write instead")] public void WriteData(byte[] data, int offset, int count); [Obsolete("Use WriteSamples instead")] public void WriteData(short[] samples, int offset, int count); public void WriteSample(float sample); public void WriteSamples(short[] samples, int offset, int count); public void WriteSamples(float[] samples, int offset, int count); protected override void Dispose(bool disposing); protected virtual void UpdateHeader(BinaryWriter writer); }
在调用上是先调用WaveIn的DataAvailable回调函数,读取其数据并写入流文件,最后保存到本地。blog
2. 播放录音
播放录音主要用到AudioFileReader、WaveOut三个类和接口
2.1 AudioFileReader
AudioFileReader主要负责读取音频文件,验证音频文件格式,对外部提供读取数据接口,具体以下:
public class AudioFileReader : WaveStream, ISampleProvider { public AudioFileReader(string fileName); public string FileName { get; } public override WaveFormat WaveFormat { get; } public override long Length { get; } public override long Position { get; set; } public float Volume { get; set; } public override int Read(byte[] buffer, int offset, int count); public int Read(float[] buffer, int offset, int count); protected override void Dispose(bool disposing); }
2.2 WaveOut
WaveOut的工做是播放音频,它调用AudioFileReader.Read进行数据读取,对读取的数据进行播放,主要工做流程是从获取数据,并将数据进行播放成音频
public class WaveOut : IWavePlayer, IDisposable, IWavePosition { public WaveOut(); public WaveOut(IntPtr windowHandle); public WaveOut(WaveCallbackInfo callbackInfo); ~WaveOut(); public static int DeviceCount { get; } public PlaybackState PlaybackState { get; } public WaveFormat OutputWaveFormat { get; } public int DeviceNumber { get; set; } public int NumberOfBuffers { get; set; } public int DesiredLatency { get; set; } public float Volume { get; set; } public event EventHandler<StoppedEventArgs> PlaybackStopped; public static WaveOutCapabilities GetCapabilities(int devNumber); public void Dispose(); public long GetPosition(); public void Init(IWaveProvider waveProvider); public void Pause(); public void Play(); public void Resume(); public void Stop(); protected void Dispose(bool disposing); }
3. 波形图绘制
录音时绘制波形图须要在DataAviliable回调函数中获取音频数据并将其从byte[]转换为float[],而后用float[]数据作为波形图的输入便可,这个过程源码上写一个数据包的波形图数据为waveSource.WaveFormat.SampleRate / 100,原理上我还没搞懂,可是的确是这么操做显示是对的,具体以下:
private void waveSource_DataAvailable(object sender, WaveInEventArgs e) { if (waveFile != null) { waveFile.Write(e.Buffer, 0, e.BytesRecorded); waveFile.Flush(); float[] sts = new float[e.Buffer.Length / 2]; int outIndex = 0; for (int n = 0; n < e.Buffer.Length; n += 2) { sts[outIndex++] = BitConverter.ToInt16(e.Buffer, n) / 32768f; } for (int n = 0; n < sts.Length; n += channels) { Add(sts[n]); } } }
须要注意的是WaveFormat的通道数设置、PCM的格式设置,上述代码都是基于通道数为二、PCM为16bit的状况下,如这两项修改会发生转换和调用失败等问题
可调试Demo:示例Demo