在使用C#进行录音和播放录音功能上,使用NAudio是个不错的选择。

  NAudio是个开源,相对功能比较全面的类库,它包含录音、播放录音、格式转换、混音调整等操作,具体可以去Github上看看介绍和源码,附:Git地址

  我使用到的是录制和播放wav格式的音频,对应调用NAudio的WaveFileWriter和WaveFileReader类进行开发,从源码上看原理就是

  1. 根据上层传入的因为文件类型格式(mp3、wav等格式)定义进行创建流文件,并添加对应header和format等信息
  2. 调用WinAPI进行数据采集,实时读取其传入数据并保存至上述流文件中
  3. 保存

附: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的更多相关文章

  1. [Android] 录音与播放录音实现

    http://blog.csdn.net/cxf7394373/article/details/8313980 android开发文档中有一个关于录音的类MediaRecord,一张图介绍了基本的流程 ...

  2. MT6737 Android N 平台 Audio系统学习----录音到播放录音流程分析

    http://blog.csdn.net/u014310046/article/details/54133688 本文将从主mic录音到播放流程来进行学习mtk audio系统架构.  在AudioF ...

  3. C# NAudio录音和播放音频文件-实时绘制音频波形图(从音频流数据获取,而非设备获取)

    NAudio的录音和播放录音都有对应的类,我在使用Wav格式进行录音和播放录音时使用的类时WaveIn和WaveOut,这两个类是对功能的回调和一些事件触发. 在WaveIn和WaveOut之外还有对 ...

  4. C# NAudio录音和播放音频文件及实时绘制音频波形图(从音频流数据获取,而非设备获取)

    下午写了一篇关于NAudio的录音.播放和波形图的博客,不太满意,感觉写的太乱,又总结了下 NAudio是个相对成熟.开源的C#音频开发工具,它包含录音.播放录音.格式转换.混音调整等功能.本次介绍主 ...

  5. AVFoundation之录音及播放

    录音 在开始录音前,要把会话方式设置成AVAudioSessionCategoryPlayAndRecord //设置为播放和录音状态,以便可以在录制完之后播放录音 AVAudioSession *s ...

  6. IOS关于录音,播放实现总结

    //音频录制(标准过程5,9更新) 准备:导入AVFoundation框架及头文件 1 设置会话类型,允许播放及录音AVAudioSession *audioSession = [AVAudioSes ...

  7. Android开发教程 录音和播放

    首先要了解andriod开发中andriod多媒体框架包含了什么,它包含了获取和编码多种音频格式的支持,因此你几耍轻松把音频合并到你的应用中,若设备支持,使用MediaRecorder APIs便可以 ...

  8. Android平台下实现录音及播放录音功能的简介

    录音及播放的方法如下: package com.example.audiorecord; import java.io.File; import java.io.IOException; import ...

  9. .net简单录音和播放音频文件代码

    本代码特点:不用DirectX ,对于C/S .B/S都适用. 方法: //mciSendStrin.是用来播放多媒体文件的API指令,可以播放MPEG,AVI,WAV,MP3,等等,下面介绍一下它的 ...

随机推荐

  1. Tian Tian 菾菾 导游 陪同

    自画像系列是梵高的代表作之一,他是一位自学成才的画家,下笔完全自由,主观提取了当时印象派画家学到的技巧,在这幅画中,我们可以看到,颜色在画中的堆叠,色彩与笔在画中表现的形态,都表现出,梵高在他作画中内 ...

  2. 在GitHub上分享自己的项目

    GitHub主要是用作基于Git的分布式版本管理系统的库,可以保存和管理自己的代码,而且主要用作代码的合作开发. 注册GitHub后你就会有0.3G的免费空间,不过只能创建公开项目,这也满足代码分享的 ...

  3. 压力测试(九)-Jmeter压测课程总结和架构浅析

    安装常见问题 1.问题 [root@iZwz95j86y235aroi85ht0Z bin]# ./jmeter-server Created remote object: UnicastServer ...

  4. Nginx之反向代理配置(一)

    前文我们聊了下Nginx作为web服务器配置https.日志模块的常用配置.rewrite模块重写用户请求的url,回顾请参考https://www.cnblogs.com/qiuhom-1874/p ...

  5. CSS 实现元素较宽不能被完全展示时将其隐藏

    首发于本人的博客 varnull.cn 遇到一个需求,需要实现的样式是固定宽度的容器里一排显示若干个标签,数量不定,每个标签的长度也不定.当到了某个标签不能被完全展示下时则不显示.大致效果如下,标签只 ...

  6. Python学习笔记--迭代

    在Python中,迭代是通过for ... in来实现.只要是可迭代的对象都可以用for ... in来进行历遍. 常用的有list.tuple.dict等.举例如下: 列表的迭代: L=[1,2,3 ...

  7. 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问题 今天在装 ...

  8. Apache Druid 的集群设计与工作流程

    导读:本文将描述 Apache Druid 的基本集群架构,说明架构中各进程的作用.并从数据写入和数据查询两个角度来说明 Druid 架构的工作流程. 关注公众号 MageByte,设置星标点「在看」 ...

  9. Await/Async

    Async其实就是Generator函数的语法糖. 啥是语法糖?就是一种更容易让人理解,代码可读性更高的另外一种语法. const asyncRead = async function(){ cons ...

  10. 如何在Flutter中使用flutter_markdown

    很多博客,论坛都支持markdown语法,flutter也有支持markdown语法的插件flutter_markdown 安装依赖 dependencies: flutter: sdk: flutt ...