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,等等,下面介绍一下它的 ...
随机推荐
- Tian Tian 菾菾 导游 陪同
自画像系列是梵高的代表作之一,他是一位自学成才的画家,下笔完全自由,主观提取了当时印象派画家学到的技巧,在这幅画中,我们可以看到,颜色在画中的堆叠,色彩与笔在画中表现的形态,都表现出,梵高在他作画中内 ...
- 在GitHub上分享自己的项目
GitHub主要是用作基于Git的分布式版本管理系统的库,可以保存和管理自己的代码,而且主要用作代码的合作开发. 注册GitHub后你就会有0.3G的免费空间,不过只能创建公开项目,这也满足代码分享的 ...
- 压力测试(九)-Jmeter压测课程总结和架构浅析
安装常见问题 1.问题 [root@iZwz95j86y235aroi85ht0Z bin]# ./jmeter-server Created remote object: UnicastServer ...
- Nginx之反向代理配置(一)
前文我们聊了下Nginx作为web服务器配置https.日志模块的常用配置.rewrite模块重写用户请求的url,回顾请参考https://www.cnblogs.com/qiuhom-1874/p ...
- CSS 实现元素较宽不能被完全展示时将其隐藏
首发于本人的博客 varnull.cn 遇到一个需求,需要实现的样式是固定宽度的容器里一排显示若干个标签,数量不定,每个标签的长度也不定.当到了某个标签不能被完全展示下时则不显示.大致效果如下,标签只 ...
- Python学习笔记--迭代
在Python中,迭代是通过for ... in来实现.只要是可迭代的对象都可以用for ... in来进行历遍. 常用的有list.tuple.dict等.举例如下: 列表的迭代: L=[1,2,3 ...
- PySide2的This application failed to start because no Qt platform plugin could be initialized解决方式
解决PySide2的This application failed to start because no Qt platform plugin could be initialized问题 今天在装 ...
- Apache Druid 的集群设计与工作流程
导读:本文将描述 Apache Druid 的基本集群架构,说明架构中各进程的作用.并从数据写入和数据查询两个角度来说明 Druid 架构的工作流程. 关注公众号 MageByte,设置星标点「在看」 ...
- Await/Async
Async其实就是Generator函数的语法糖. 啥是语法糖?就是一种更容易让人理解,代码可读性更高的另外一种语法. const asyncRead = async function(){ cons ...
- 如何在Flutter中使用flutter_markdown
很多博客,论坛都支持markdown语法,flutter也有支持markdown语法的插件flutter_markdown 安装依赖 dependencies: flutter: sdk: flutt ...