在使用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. Vue.observable()使用方法

    前言 随着组件的细化,就会遇到多组件状态共享的情况, Vuex当然可以解决这类问题,不过就像 Vuex官方文档所说的,如果应用不够大,为避免代码繁琐冗余,最好不要使用它,今天我们介绍的是 vue.js ...

  2. 优秀的Spring Cloud开源项目整理推荐

    无论是对于初学者,还是有一定工作经验的程序员来讲,Spring Cloud开源项目都是一笔宝贵的财富.下面给大家整理了十个优秀的开源项目,分别是spring-cloud-examples.spaasc ...

  3. 从0开发3D引擎(补充):介绍领域驱动设计

    我们使用领域驱动设计(英文缩写为DDD)的方法来设计引擎,在引擎开发的过程中,领域模型会不断地演化. 本文介绍本系列使用的领域驱动设计思想的相关概念和知识点,给出了相关的资料. 上一篇博文 从0开发3 ...

  4. MySQL的字符集和乱码问题

    1.字符集知识 #概述 .字符集是一套文字符号及其编码.比较规则的集合,第一个计算机字符串ASC2 .mysql数据库字符集包括字符集(character)和 校对规则,其中字符集使用来定义mysql ...

  5. Electron打包H5网页为桌面运行程序

    一.安装配置环境 Electron(一种桌面应用程序运行时),Electron 把 Chromium 和 Node 合并到一个单独的运行时里面,很适合开发桌面 web 形式的应用程序,通过Node它提 ...

  6. Postgresql存放数组形式的数据

    Postgres 数据库允许把字段定义为可变长度的数组.数据类型既可以是内置类型,也可以是用户自定义的类型或枚举类型. 例如: 创建表 create table demo(name text,subj ...

  7. 全面认识HBase架构(建议收藏)

    在网上看过很多HBaes架构相关的文章,内容深浅不一,直到发现了一篇MapR官网的文章https://mapr.com/blog/in-depth-look-hbase-architecture/#. ...

  8. 峰哥说技术:04-Spring Boot基本配置

    Spring Boot深度课程系列 峰哥说技术—2020庚子年重磅推出.战胜病毒.我们在行动 04 Spring Boot基本配置 1)容器的相关配置 在Spring Boot中可以内置Tomcat. ...

  9. 025.掌握Service-SVC基础使用

    一 Service简介 1.1 Service概念 Service是Kubernetes的核心概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到 ...

  10. SpringBoot突报java.lang.NoSuchFieldError分析

    SpringBoot项目,引了一个内部的工具包,竟然导致启动失败,报找不到freemarker Configuration类的一个属性,网上的解法都大同小异,最终用了自己的办法解决,花点时间记录下来, ...