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,等等,下面介绍一下它的 ...
随机推荐
- HDU——算法练习1000 1089-1096
全篇都是讲数字之间的运算的: 由上自下难度逐渐升级 ,没耐心者建议一拉到底: 1000: Problem Description Calculate A + B. Input Each line ...
- Django中使用websocket并实现简易聊天室
django使用websocket并实现简易聊天室 django默认只支持http协议 如果你想让django即支持http协议又支持websocket协议,则需要做以下配置 前期配置 前提需要安装c ...
- 【原创】从零开始搭建Electron+Vue+Webpack项目框架(五)预加载和Electron自动更新
导航: (一)Electron跑起来(二)从零搭建Vue全家桶+webpack项目框架(三)Electron+Vue+Webpack,联合调试整个项目(四)Electron配置润色(五)预加载及自动更 ...
- 教你如何去除电脑QQ聊天窗口上的广告?
当打开QQ聊天窗口时,就如下图一样各种广告不停地闪啊闪的,我没强迫症但是我也不喜欢看. 像咱们这些没有钱开会员又不喜欢整天看电脑QQ聊天窗口上的广告的"穷人们".那该咋办呢?好了, ...
- CVE-2019-0708 远程桌面漏洞复现
漏洞影响Windows版本: Windows XP SP3 x86Windows XP Professional x64 Edition SP2Windows XP Embedded SP3 x86W ...
- Java程序员考研失败后的面试经历,oppo、VIVO、等面经
温馨提示:有些可能会遗漏个别问题,都是最近一周的面试,有点忘了. 浪潮(一面挂) 你是网络工程的?对网络很了解? 解释一下什么是广播域 怎么划分子网 说一下CSS的几种分类器 数据库中有哪些聚集函 ...
- celery异步任务框架
目录 Celery 一.官方 二.Celery异步任务框架 Celery架构图 消息中间件 任务执行单元 任务结果存储 三.使用场景 四.Celery的安装配置 五.两种celery任务结构:提倡用包 ...
- Python之接口测试(一)
前言 之前我们已经学会了利用JMeter工具进行接口测试,今天我们学习一下如何利用python进行接口测试. 一:发送get请求 import requests,json url = 'http:// ...
- 2020 新个税算的头疼?会 python,这都不是事儿...
背景 今年疫情复工后,财务小姐姐给我们普及了2020年新个税的算法.. 听完之后的感觉就是:恩,原来是这么回事! 虽然是个小工薪阶级,但是对于扣多少税还是很关心的.于是拿起笔算了算2月份的个税,产生了 ...
- 18 JpaRepository和JpaSpecificationExecutor
继承JpaRepository后的方法列表 JpaRepository findAll() List<T> findAll(Sort) List<T> findAll(Iter ...