C# 录音和播放录音-NAudio
在使用C#进行录音和播放录音功能上,使用NAudio是个不错的选择。
NAudio是个开源,相对功能比较全面的类库,它包含录音、播放录音、格式转换、混音调整等操作,具体可以去Github上看看介绍和源码,附:Git地址
我使用到的是录制和播放wav格式的音频,对应调用NAudio的WaveFileWriter和WaveFileReader类进行开发,从源码上看原理就是
- 根据上层传入的因为文件类型格式(mp3、wav等格式)定义进行创建流文件,并添加对应header和format等信息
- 调用WinAPI进行数据采集,实时读取其传入数据并保存至上述流文件中
- 保存
附:WaveFileWriter源码
using System;
using System.IO;
using NAudio.Wave.SampleProviders;
using NAudio.Utils; // ReSharper disable once CheckNamespace
namespace NAudio.Wave
{
/// <summary>
/// This class writes WAV data to a .wav file on disk
/// </summary>
public class WaveFileWriter : Stream
{
private Stream outStream;
private readonly BinaryWriter writer;
private long dataSizePos;
private long factSampleCountPos;
private long dataChunkSize;
private readonly WaveFormat format;
private readonly string filename; /// <summary>
/// Creates a 16 bit Wave File from an ISampleProvider
/// BEWARE: the source provider must not return data indefinitely
/// </summary>
/// <param name="filename">The filename to write to</param>
/// <param name="sourceProvider">The source sample provider</param>
public static void CreateWaveFile16(string filename, ISampleProvider sourceProvider)
{
CreateWaveFile(filename, new SampleToWaveProvider16(sourceProvider));
} /// <summary>
/// Creates a Wave file by reading all the data from a WaveProvider
/// BEWARE: the WaveProvider MUST return 0 from its Read method when it is finished,
/// or the Wave File will grow indefinitely.
/// </summary>
/// <param name="filename">The filename to use</param>
/// <param name="sourceProvider">The source WaveProvider</param>
public static void CreateWaveFile(string filename, IWaveProvider sourceProvider)
{
using (var writer = new WaveFileWriter(filename, sourceProvider.WaveFormat))
{
var buffer = new byte[sourceProvider.WaveFormat.AverageBytesPerSecond * ];
while (true)
{
int bytesRead = sourceProvider.Read(buffer, , buffer.Length);
if (bytesRead == )
{
// end of source provider
break;
}
// Write will throw exception if WAV file becomes too large
writer.Write(buffer, , bytesRead);
}
}
} /// <summary>
/// Writes to a stream by reading all the data from a WaveProvider
/// BEWARE: the WaveProvider MUST return 0 from its Read method when it is finished,
/// or the Wave File will grow indefinitely.
/// </summary>
/// <param name="outStream">The stream the method will output to</param>
/// <param name="sourceProvider">The source WaveProvider</param>
public static void WriteWavFileToStream(Stream outStream, IWaveProvider sourceProvider)
{
using (var writer = new WaveFileWriter(new IgnoreDisposeStream(outStream), sourceProvider.WaveFormat))
{
var buffer = new byte[sourceProvider.WaveFormat.AverageBytesPerSecond * ];
while(true)
{
var bytesRead = sourceProvider.Read(buffer, , buffer.Length);
if (bytesRead == )
{
// end of source provider
outStream.Flush();
break;
} writer.Write(buffer, , bytesRead);
}
}
} /// <summary>
/// WaveFileWriter that actually writes to a stream
/// </summary>
/// <param name="outStream">Stream to be written to</param>
/// <param name="format">Wave format to use</param>
public WaveFileWriter(Stream outStream, WaveFormat format)
{
this.outStream = outStream;
this.format = format;
writer = new BinaryWriter(outStream, System.Text.Encoding.UTF8);
writer.Write(System.Text.Encoding.UTF8.GetBytes("RIFF"));
writer.Write((int)); // placeholder
writer.Write(System.Text.Encoding.UTF8.GetBytes("WAVE")); writer.Write(System.Text.Encoding.UTF8.GetBytes("fmt "));
format.Serialize(writer); CreateFactChunk();
WriteDataChunkHeader();
} /// <summary>
/// Creates a new WaveFileWriter
/// </summary>
/// <param name="filename">The filename to write to</param>
/// <param name="format">The Wave Format of the output data</param>
public WaveFileWriter(string filename, WaveFormat format)
: this(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read), format)
{
this.filename = filename;
} private void WriteDataChunkHeader()
{
writer.Write(System.Text.Encoding.UTF8.GetBytes("data"));
dataSizePos = outStream.Position;
writer.Write((int)); // placeholder
} private void CreateFactChunk()
{
if (HasFactChunk())
{
writer.Write(System.Text.Encoding.UTF8.GetBytes("fact"));
writer.Write((int));
factSampleCountPos = outStream.Position;
writer.Write((int)); // number of samples
}
} private bool HasFactChunk()
{
return format.Encoding != WaveFormatEncoding.Pcm &&
format.BitsPerSample != ;
} /// <summary>
/// The wave file name or null if not applicable
/// </summary>
public string Filename => filename; /// <summary>
/// Number of bytes of audio in the data chunk
/// </summary>
public override long Length => dataChunkSize; /// <summary>
/// Total time (calculated from Length and average bytes per second)
/// </summary>
public TimeSpan TotalTime => TimeSpan.FromSeconds((double)Length / WaveFormat.AverageBytesPerSecond); /// <summary>
/// WaveFormat of this wave file
/// </summary>
public WaveFormat WaveFormat => format; /// <summary>
/// Returns false: Cannot read from a WaveFileWriter
/// </summary>
public override bool CanRead => false; /// <summary>
/// Returns true: Can write to a WaveFileWriter
/// </summary>
public override bool CanWrite => true; /// <summary>
/// Returns false: Cannot seek within a WaveFileWriter
/// </summary>
public override bool CanSeek => false; /// <summary>
/// Read is not supported for a WaveFileWriter
/// </summary>
public override int Read(byte[] buffer, int offset, int count)
{
throw new InvalidOperationException("Cannot read from a WaveFileWriter");
} /// <summary>
/// Seek is not supported for a WaveFileWriter
/// </summary>
public override long Seek(long offset, SeekOrigin origin)
{
throw new InvalidOperationException("Cannot seek within a WaveFileWriter");
} /// <summary>
/// SetLength is not supported for WaveFileWriter
/// </summary>
/// <param name="value"></param>
public override void SetLength(long value)
{
throw new InvalidOperationException("Cannot set length of a WaveFileWriter");
} /// <summary>
/// Gets the Position in the WaveFile (i.e. number of bytes written so far)
/// </summary>
public override long Position
{
get => dataChunkSize;
set => throw new InvalidOperationException("Repositioning a WaveFileWriter is not supported");
} /// <summary>
/// Appends bytes to the WaveFile (assumes they are already in the correct format)
/// </summary>
/// <param name="data">the buffer containing the wave data</param>
/// <param name="offset">the offset from which to start writing</param>
/// <param name="count">the number of bytes to write</param>
[Obsolete("Use Write instead")]
public void WriteData(byte[] data, int offset, int count)
{
Write(data, offset, count);
} /// <summary>
/// Appends bytes to the WaveFile (assumes they are already in the correct format)
/// </summary>
/// <param name="data">the buffer containing the wave data</param>
/// <param name="offset">the offset from which to start writing</param>
/// <param name="count">the number of bytes to write</param>
public override void Write(byte[] data, int offset, int count)
{
if (outStream.Length + count > UInt32.MaxValue)
throw new ArgumentException("WAV file too large", nameof(count));
outStream.Write(data, offset, count);
dataChunkSize += count;
} private readonly byte[] value24 = new byte[]; // keep this around to save us creating it every time /// <summary>
/// Writes a single sample to the Wave file
/// </summary>
/// <param name="sample">the sample to write (assumed floating point with 1.0f as max value)</param>
public void WriteSample(float sample)
{
if (WaveFormat.BitsPerSample == )
{
writer.Write((Int16)(Int16.MaxValue * sample));
dataChunkSize += ;
}
else if (WaveFormat.BitsPerSample == )
{
var value = BitConverter.GetBytes((Int32)(Int32.MaxValue * sample));
value24[] = value[];
value24[] = value[];
value24[] = value[];
writer.Write(value24);
dataChunkSize += ;
}
else if (WaveFormat.BitsPerSample == && WaveFormat.Encoding == WaveFormatEncoding.Extensible)
{
writer.Write(UInt16.MaxValue * (Int32)sample);
dataChunkSize += ;
}
else if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
{
writer.Write(sample);
dataChunkSize += ;
}
else
{
throw new InvalidOperationException("Only 16, 24 or 32 bit PCM or IEEE float audio data supported");
}
} /// <summary>
/// Writes 32 bit floating point samples to the Wave file
/// They will be converted to the appropriate bit depth depending on the WaveFormat of the WAV file
/// </summary>
/// <param name="samples">The buffer containing the floating point samples</param>
/// <param name="offset">The offset from which to start writing</param>
/// <param name="count">The number of floating point samples to write</param>
public void WriteSamples(float[] samples, int offset, int count)
{
for (int n = ; n < count; n++)
{
WriteSample(samples[offset + n]);
}
} /// <summary>
/// Writes 16 bit samples to the Wave file
/// </summary>
/// <param name="samples">The buffer containing the 16 bit samples</param>
/// <param name="offset">The offset from which to start writing</param>
/// <param name="count">The number of 16 bit samples to write</param>
[Obsolete("Use WriteSamples instead")]
public void WriteData(short[] samples, int offset, int count)
{
WriteSamples(samples, offset, count);
} /// <summary>
/// Writes 16 bit samples to the Wave file
/// </summary>
/// <param name="samples">The buffer containing the 16 bit samples</param>
/// <param name="offset">The offset from which to start writing</param>
/// <param name="count">The number of 16 bit samples to write</param>
public void WriteSamples(short[] samples, int offset, int count)
{
// 16 bit PCM data
if (WaveFormat.BitsPerSample == )
{
for (int sample = ; sample < count; sample++)
{
writer.Write(samples[sample + offset]);
}
dataChunkSize += (count * );
}
// 24 bit PCM data
else if (WaveFormat.BitsPerSample == )
{
for (int sample = ; sample < count; sample++)
{
var value = BitConverter.GetBytes(UInt16.MaxValue * (Int32)samples[sample + offset]);
value24[] = value[];
value24[] = value[];
value24[] = value[];
writer.Write(value24);
}
dataChunkSize += (count * );
}
// 32 bit PCM data
else if (WaveFormat.BitsPerSample == && WaveFormat.Encoding == WaveFormatEncoding.Extensible)
{
for (int sample = ; sample < count; sample++)
{
writer.Write(UInt16.MaxValue * (Int32)samples[sample + offset]);
}
dataChunkSize += (count * );
}
// IEEE float data
else if (WaveFormat.BitsPerSample == && WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
{
for (int sample = ; sample < count; sample++)
{
writer.Write((float)samples[sample + offset] / (float)(Int16.MaxValue + ));
}
dataChunkSize += (count * );
}
else
{
throw new InvalidOperationException("Only 16, 24 or 32 bit PCM or IEEE float audio data supported");
}
} /// <summary>
/// Ensures data is written to disk
/// Also updates header, so that WAV file will be valid up to the point currently written
/// </summary>
public override void Flush()
{
var pos = writer.BaseStream.Position;
UpdateHeader(writer);
writer.BaseStream.Position = pos;
} #region IDisposable Members /// <summary>
/// Actually performs the close,making sure the header contains the correct data
/// </summary>
/// <param name="disposing">True if called from <see>Dispose</see></param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (outStream != null)
{
try
{
UpdateHeader(writer);
}
finally
{
// in a finally block as we don't want the FileStream to run its disposer in
// the GC thread if the code above caused an IOException (e.g. due to disk full)
outStream.Dispose(); // will close the underlying base stream
outStream = null;
}
}
}
} /// <summary>
/// Updates the header with file size information
/// </summary>
protected virtual void UpdateHeader(BinaryWriter writer)
{
writer.Flush();
UpdateRiffChunk(writer);
UpdateFactChunk(writer);
UpdateDataChunk(writer);
} private void UpdateDataChunk(BinaryWriter writer)
{
writer.Seek((int)dataSizePos, SeekOrigin.Begin);
writer.Write((UInt32)dataChunkSize);
} private void UpdateRiffChunk(BinaryWriter writer)
{
writer.Seek(, SeekOrigin.Begin);
writer.Write((UInt32)(outStream.Length - ));
} private void UpdateFactChunk(BinaryWriter writer)
{
if (HasFactChunk())
{
int bitsPerSample = (format.BitsPerSample * format.Channels);
if (bitsPerSample != )
{
writer.Seek((int)factSampleCountPos, SeekOrigin.Begin); writer.Write((int)((dataChunkSize * ) / bitsPerSample));
}
}
} /// <summary>
/// Finaliser - should only be called if the user forgot to close this WaveFileWriter
/// </summary>
~WaveFileWriter()
{
System.Diagnostics.Debug.Assert(false, "WaveFileWriter was not disposed");
Dispose(false);
} #endregion
}
}
WaveFileReader和WaveFileWriter相似,只是把写流文件变成了读流文件,具体可在源码中查看。
值得注意的是,在有需要对音频进行分析处理的需求时(如VAD)可以查看其DataAvailable事件,该事件会实时回调传递音频数据(byte[]),最后强调一点这个音频数据byte数组需要注意其写入时和读取时PCM所使用的bit数,PCM分别有8/16/24/32四种,在WaveFormat.BitsPerSample属性上可以查看,根据PCM不同类型这个byte数组的真实数据转换上也要转换不同类型,8bit是一个字节、16bit是两个字节、24.....32...等,在使用时根据这个进行对应转换才是正确的数值。
附PCM类型初始化对应部分代码:
public static ISampleProvider ConvertWaveProviderIntoSampleProvider(IWaveProvider waveProvider)
{
ISampleProvider sampleProvider;
if (waveProvider.WaveFormat.Encoding == WaveFormatEncoding.Pcm)
{
// go to float
if (waveProvider.WaveFormat.BitsPerSample == )
{
sampleProvider = new Pcm8BitToSampleProvider(waveProvider);
}
else if (waveProvider.WaveFormat.BitsPerSample == )
{
sampleProvider = new Pcm16BitToSampleProvider(waveProvider);
}
else if (waveProvider.WaveFormat.BitsPerSample == )
{
sampleProvider = new Pcm24BitToSampleProvider(waveProvider);
}
else if (waveProvider.WaveFormat.BitsPerSample == )
{
sampleProvider = new Pcm32BitToSampleProvider(waveProvider);
}
else
{
throw new InvalidOperationException("Unsupported bit depth");
}
}
else if (waveProvider.WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
{
if (waveProvider.WaveFormat.BitsPerSample == )
sampleProvider = new WaveToSampleProvider64(waveProvider);
else
sampleProvider = new WaveToSampleProvider(waveProvider);
}
else
{
throw new ArgumentException("Unsupported source encoding");
}
return sampleProvider;
}
}
以上是查看源码和使用上的一些记录,具体录制和播放示例如下:示例
新接触,有些感悟,分享下
C# 录音和播放录音-NAudio的更多相关文章
- [Android] 录音与播放录音实现
http://blog.csdn.net/cxf7394373/article/details/8313980 android开发文档中有一个关于录音的类MediaRecord,一张图介绍了基本的流程 ...
- MT6737 Android N 平台 Audio系统学习----录音到播放录音流程分析
http://blog.csdn.net/u014310046/article/details/54133688 本文将从主mic录音到播放流程来进行学习mtk audio系统架构. 在AudioF ...
- C# NAudio录音和播放音频文件-实时绘制音频波形图(从音频流数据获取,而非设备获取)
NAudio的录音和播放录音都有对应的类,我在使用Wav格式进行录音和播放录音时使用的类时WaveIn和WaveOut,这两个类是对功能的回调和一些事件触发. 在WaveIn和WaveOut之外还有对 ...
- C# NAudio录音和播放音频文件及实时绘制音频波形图(从音频流数据获取,而非设备获取)
下午写了一篇关于NAudio的录音.播放和波形图的博客,不太满意,感觉写的太乱,又总结了下 NAudio是个相对成熟.开源的C#音频开发工具,它包含录音.播放录音.格式转换.混音调整等功能.本次介绍主 ...
- AVFoundation之录音及播放
录音 在开始录音前,要把会话方式设置成AVAudioSessionCategoryPlayAndRecord //设置为播放和录音状态,以便可以在录制完之后播放录音 AVAudioSession *s ...
- IOS关于录音,播放实现总结
//音频录制(标准过程5,9更新) 准备:导入AVFoundation框架及头文件 1 设置会话类型,允许播放及录音AVAudioSession *audioSession = [AVAudioSes ...
- Android开发教程 录音和播放
首先要了解andriod开发中andriod多媒体框架包含了什么,它包含了获取和编码多种音频格式的支持,因此你几耍轻松把音频合并到你的应用中,若设备支持,使用MediaRecorder APIs便可以 ...
- Android平台下实现录音及播放录音功能的简介
录音及播放的方法如下: package com.example.audiorecord; import java.io.File; import java.io.IOException; import ...
- .net简单录音和播放音频文件代码
本代码特点:不用DirectX ,对于C/S .B/S都适用. 方法: //mciSendStrin.是用来播放多媒体文件的API指令,可以播放MPEG,AVI,WAV,MP3,等等,下面介绍一下它的 ...
随机推荐
- (转)Linux设备驱动之HID驱动 源码分析
//Linux设备驱动之HID驱动 源码分析 http://blog.chinaunix.net/uid-20543183-id-1930836.html HID是Human Interface De ...
- 关于scanf与gets的区别
以下内容主要来源: scanf与gets读取字符串 scanf与gets函数读取字符串的区别 前两天有个同学问我scanf与gets的区别说了半天也没说出来个所以然,就搜了一下,scanf()和get ...
- 基于arduino的红外传感系统
一.作品背景 在这个科技飞速发展的时代,物联网已经成为了我们身边必不可少的技术模块,我这次课程设计做的是一个基于arduino+树莓派+OneNet的红外报警系统,它主要通过识别人或者动物的运动来判断 ...
- js轮询及踩过的坑
背景 下午四点,天气晴朗,阳光明媚,等着下班产品:我希望页面上的这个数据实时变化开发:···,可以,用那个叫着WebSocket的东西,再找一个封装好框架,如:mqtt(感觉自己好机智)产品:要开发好 ...
- JZOJ 3518. 【NOIP2013模拟11.6A组】进化序列(evolve)
3518. [NOIP2013模拟11.6A组]进化序列(evolve) (File IO): input:evolve.in output:evolve.out Time Limits: 1000 ...
- Codeforces Round #292 (Div. 2) C. Drazil and Factorial 515C
C. Drazil and Factorial time limit per test 2 seconds memory limit per test 256 megabytes input stan ...
- C++ const用法,看这一篇就够了!
本文主要介绍const修饰符在C++中的主要用法,下面会从两个方面进行介绍:类定义中使用const.非类定义中使用const 1. 非类定义中使用const 非类定义中使用const是指:在除了类定义 ...
- 微信小程序注册和简单配置
微信小程序注册 1.直接搜索微信小程序,按照流程进行注册 2.如果有微信公众号,可以在公众号内部点小程序,进入注册流程 小程序中的概念 开发设置 在开发设置中获取AppID和AppSecret App ...
- 使用NPOI将Excel表导入到数据库中
public string ExcelFile() { //指定文件路径, string fileName=@"d:\Stu.xls"; //创建一个文件流,并指定其中属性 usi ...
- 由世界坐标系转换到摄像机坐标系的lookAt()函数
在学习图形学和opengl的时候,都涉及到坐标转化,从物体坐标转换为世界的坐标,从世界的坐标转换为摄像机的坐标. 在世界坐标到摄像机转换的过程中常用lookAt函数得到转化矩阵.GLM官方文档对它的解 ...