概述

在 NAudio 中, 常用类型有 WaveIn, WaveOut, WaveStream, WaveFileWriter, WaveFileReader 以及接口: IWaveProvider

  1. WaveIn 表示波形输入, 例如麦克风输入, 或者计算机正在播放的音频流.
  2. WaveOut 表示波形输出, 用来播放波形音乐, 以继承了 IWaveProvider 的类型作为播放源播放音乐
  3. WaveStream 表示波形流, 它继承了 IWaveProvider, 可以用来作为播放源.
  4. WaveFileReader 继承了 WaveStream, 用来读取波形文件
  5. WaveFileWriter 继承了 Stream, 用来写入文件, 常用于保存音频录制的数据
  6. IWaveProvider 上面已经提到, 是音频播放的提供者

播放音频

常用的播放音频方式有两种, 播放波形音乐, 以及播放 MP3 音乐

  1. 播放波形音乐:

    // NAudio 中, 通过 WaveFileReader 来读取波形数据, 在实例化时, 你可以指定文件名或者是输入流, 这意味着你可以读取内存流中的音频数据
    // 但是需要注意的是, 不可以读取来自网络流的音频, 因为网络流不可以进行 Seek 操作. // 此处, 假设 ms 为一个 MemoryStream, 内存有音频数据.
    WaveFileReader reader = new WaveFileReader(ms);
    WaveOut wout = new WaveOut();
    wout.Init(reader); // 通过 IWaveProvider 为音频输出初始化
    wout.Play(); // 至此, wout 将从指定的 reader 中提供的数据进行播放
  2. 播放 MP3 音乐:

    // 播放 MP3 音乐其实与播放波形音乐没有太大区别, 只不过将 WaveFileReader 换成了 Mp3FileReader 罢了
    // 另外, 也可以使用通用的 Reader, MediaFoundationReader, 它既可以读取波形音乐, 也可以读取 MP3 // 此处, 假设 ms 为一个 MemoryStream, 内存有音频数据.
    Mp3FileReader reader = new Mp3FileReader(ms);
    WaveOut wout = new WaveOut();
    wout.Init(reader);
    wout.Play();

音频录制

  1. 录制麦克风输入

    // 借助 WaveIn 类, 我们可以轻易的捕获麦克风输入, 在每一次录制到数据时, 将数据写入到文件或其他流, 这就实现了保存录音
    // 在保存波形文件时需要借助 WaveFileWriter, 当然, 如果你想保存为其他格式, 也可以使用其它的 Writer, 例如 CurWaveFileWriter 以及
    // AiffFileWriter, 美中不足的是没有直接写入到 MP3 的 FileWriter
    // 需要注意的是, 如果你是用的桌面程序, 那么你可以直接使用 WaveIn, 其回调基于 Windows 消息, 所以无法在控制台应用中使用 WaveIn
    // 如果要在控制台应用中实现录音, 只需要使用 WaveInEvent, 它的回调基于事件而不是 Windows 消息, 所以可以通用 WaveIn cap = new WaveIn(); // cap, capture
    WaveFileWriter writer = new WaveFileWriter();
    cap.DataAvailable += (s, args) => writer.Write(args.Buffer, 0, args.BytesRecorded); // 订阅事件
    cap.StartRecording(); // 开始录制 // 结束录制时:
    cap.StopRecording(); // 停止录制
    writer.Close(); // 关闭 FileWriter, 保存数据 // 另外, 除了使用 WaveIn, 你还可以使用 WasapiCapture, 它与 WaveIn 的使用方式是一致的, 可以用来录制麦克风
    // Wasapi 全称 Windows Audio Session Application Programming Interface (Windows音频会话应用编程接口)
    // 具体 WaveIn, WaveInEvent, WasapiCapture 的性能, 笔者还没有测试过, 但估计不会有太大差异.
    // 提示: WasapiCapture 和 WasapiLoopbackCapture 位于 NAudio.Wave 命名空间下
  2. 录制声卡输出

    // 录制声卡输出, 也就是录制计算机正在播放的声音, 借助 WasapiLoopbackCapture 即可简单实现, 使用方式与 WasapiCapture 无异
    
    WasapiLoopbackCapture cap = new WasapiLoopbackCapture();
    WaveFileWriter writer = new WaveFileWriter();
    cap.DataAvailable += (s, args) => writer.Write(args.Buffer, 0, args.BytesRecorded);
    cap.StartRecording();

高级应用

  1. 获取计算机实时播放音量大小

    // 其实这个是基于刚刚的录制声卡输出的, 录制时的回调中, Buffer, BytesRecorded 指定了此次录制的数据 (缓冲区和数据长度)
    // 而这些数据, 其实是计算机对声音的采样(Sample), 具体的采样格式可以查看 WasapiLoopbackCapture 实例的 WaveForamt
    // 波形格式中的 Encoding 与 BitsPerSample 是我们所需要的. 一般默认的 Encoding 是 IeeeFloat, 也就是每一个采样都是
    // 一个浮点数, 而 BitsPerSample 也就是 32 了. 通过 BitConverter.ToSingle() 我们可以从缓冲区中取得浮点数
    // 遍历, 每 32 位一个浮点数, 最终取最大值, 这就是我们所需要的音量了 float volume;
    WasapiLoopbackCapture cap = new WasapiLoopbackCapture();
    cap.DataAvailable += (s, args) => volume = Enumerable
    .Range(0, args.BytesRecorded / 4) // 每一个采样的位置
    .Select(i => BitConverter.ToSingle(args.Buffer, i * 4)) // 获取每一个采样
    .Aggregate((v1, v2) => v1 > v2 ? v1 : v2); // 找到值最大的采样
  2. 实现音乐可视化

    // 既然我们已经知道了, 那些数据都是一个个的采样, 自然也可以通过它们来绘制频谱, 只需要进行快速傅里叶变换即可
    // 而且有意思的是, NAudio 也为我们准备好了快速傅里叶变换的方法, 位于 NAudio.Dsp 命名空间下
    // 提示: 进行傅里叶变换所需要的复数(Complex)类也位于 NAudio.Dsp 命名空间, 它有两个字段, X(实部) 与 Y(虚部)
    // 下面给出在 IeeeFloat 格式下的音乐可视化的简单示例:
    WasapiLoopbackCapture cap = new WasapiLoopbackCapture();
    cap.DataAvailable += (s, args) =>
    {
    float[] samples = Enumerable
    .Range(0, args.BytesRecorded / 4)
    .Select(i => BitConverter.ToSingle(args.Buffer, i * 4))
    .ToArray(); // 获取采样 int log = (int)Math.Ceiling(Math.Log(samples.Length, 2));
    float[] filledSamples = new float[(int)Math.Pow(2, log)];
    Array.Copy(samples, filledSamples, samples.Length); // 填充数据 int sampleRate = (s as WasapiLoopbackCapture).WaveFormat.SampleRate; // 获取采样率
    Complex[] complexSrc = filledSamples.Select((v, i) =>
    {
    double deg = i / (double)sampleRate * Math.PI * 2; // 获取当前采样率在圆上对应的角度 (弧度制)
    return new Complex()
    {
    X = (float)(Math.Cos(deg) * v),
    Y = (float)(Math.Sin(deg) * v)
    };
    }).ToArray(); // 将采样转换为对应的复数 (缠绕到圆) FastFourierTransform.FFT(false, log, complexSrc); // 进行傅里叶变换
    double[] result = complexSrc.Select(v => Math.Sqrt(v.X * v.X + v.Y * v.Y)).ToArray(); // 取得结果
    };
  3. 音频格式转换

    // 对于 Wave, CueWave, Aiff, 这些格式都有其对应的 FileWriter, 我们可以直接调用其 Writer 的 Create***File 来
    // 从 IWaveProvider 创建对应格式的文件. 对于 MP3 这类没有 FileWriter 的格式, 可以调用 MediaFoundationEncoder // 例如一个文件, "./Disconnected.mp3", 我们要将它转换为 wav 格式, 只需要使用下面的代码, CurWave 与 Aiff 同理
    using (Mp3FileReader reader = new Mp3FileReader("./Disconnected.mp3"))
    WaveFileWriter.CreateWaveFile("./Disconnected.wav", reader); // 从 IWaveProvider 创建 MP3 文件, 假如一个 WaveFileReader 为 src
    MediaFoundationEncoder.EncodeToMp3(src, "./NewMp3.mp3");

提示

对于刚刚所说的音频录制, 采样的格式有一点需要注意, 将数据转换为一个 float 数组后, 其中还需要区分音频通道, 例如一般音乐是双通道, WaveFormat 的 Channel 为 2, 那么在 float 数组中, 每两个采样为一组, 一组采样中每一个采样都是一个通道在当前时间内的采样.

以双通道距离, 下图中, 采样数据中每一个圆圈都表示一个 float 值, 那么每两个采样时间点相同, 而各个通道的采样就是每一组中每一个采样

所以对于我们刚刚进行的音乐可视化, 严格意义上来讲, 还需要区分通道

示例

本文提到的部分内容在 github.com/SlimeNull/AudioTest 仓库中有示例, 例如音频可视化, 音频录制, 以及其他零星的示例


如有错误, 还请指出

[C#] NAudio 库的各种常用使用方式: 播放 录制 转码 音频可视化的更多相关文章

  1. [C#] 使用 NAudio 实现音频可视化

    预览: 捕捉声卡输出: 实现音频可视化, 第一步就是获得音频采样, 这里我们选择使用计算机正在播放的音频作为采样源进行处理: NAudio 中, 可以借助 WasapiLoopbackCapture ...

  2. 前后端常用通讯方式-- ajax 、websocket

    一.前后端常用通讯方式 1. ajax  浏览器发起请求,服务器返回数据,服务器不能主动返回数据,要实现实时数据交互只能是ajax轮询(让浏览器隔个几秒就发送一次请求,然后更新客户端显示.这种方式实际 ...

  3. jQuery中ajax的4种常用请求方式

    jQuery中ajax的4种常用请求方式: 1.$.ajax()返回其创建的 XMLHttpRequest 对象. $.ajax() 只有一个参数:参数 key/value 对象,包含各配置及回调函数 ...

  4. iOS代码加密常用加密方式

    iOS代码加密常用加密方式 iOS代码加密常用加密方式,常见的iOS代码加密常用加密方式算法包括MD5加密.AES加密.BASE64加密,三大算法iOS代码加密是如何进行加密的,且看下文 MD5 iO ...

  5. DataGridView 中添加CheckBox和常用处理方式 .

    DataGridView 中添加CheckBox和常用处理方式 文章1 转载:http://blog.csdn.net/pinkey1987/article/details/5267934 DataG ...

  6. Linux共享库两种加载方式简述

      Linux共享库两种加载方式简述  动态库技术通常能减少程序的大小,节省空间,提高效率,具有很高的灵活性,对于升级软件版本也更加容易.与静态库不同,动态库里面的函数不是执行程序本身 的一部分,而是 ...

  7. python常用执行方式&变量&input函数

    linux系统中执行py文件方式:  ./a.py 需要执行权限 chmod -R 777(最大权限) 常用执行方式: 1. ./a.py2. python a.py 文件内部头加上 #!/usr/b ...

  8. RAC集群数据库连库代码示例(jdbc thin方式,非oci)

    1.RAC集群数据库连库代码示例(jdbc thin方式,非oci):jdbc.driverClassName=oracle.jdbc.driver.OracleDriverjdbc.url=jdbc ...

  9. Linux 常用分区方式

    1 分两个区 主目录:/ 交换分区:swap 2 常用分区方式,以使用100G空间安装linux为例 引导分区: 挂载点/boot,分区格式ext4,500M以内即可 交换分区: 无挂载点,分区格式选 ...

随机推荐

  1. vue核心---虚拟dom的实现

    生成dom的过程 由vue模板生成虚拟dom 虚拟dom转换成真实dom渲染到html页面 代码实现 要实现的真实dom <div id="box"> <p cl ...

  2. 操作系统---IO权限管理和敏感指令

    简化版 使用IOPL设置一个特权级的用户程序对所有端口的访问权限,使用I/O位图对一个特权级的用户程序设置个性化的端口访问权限(能访问部分端口.不能访问另外的端口). 用户程序的CPL<IOPL ...

  3. Docker搭建HAproxy+tomcat 实现高可用

    构建业务镜像1创建tomcat-app1和tomcat-app2两个目录,代表不同的两个基于tomcat的业务.准备tomcat的配置文件[root@localhost ~]#mkdir -p /da ...

  4. rsyslog日志总结

    rsyslog日志总结 一 rsyslog介绍 syslogd被rsyslog取代 将日志写入数据库 可以利用模块和插件控制输入输出 rsyslog程序管理本地和远程日志 安装软件 根据需求修改配置文 ...

  5. 【Arduino学习笔记06】上拉电阻和下拉电阻

    为什么要用上拉电阻和下拉电阻?--避免输入引脚处于"悬空"状态 下图是一个没有使用上拉电阻/下拉电阻的电路图: 在按键没有按下时,要读取的输入引脚没有连接到任何东西,这种状态就称为 ...

  6. 分布式session实现方式

    一.背景 在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理. 如果不做任何处理的话,用户将出现频繁登录的现象,比如集群中存在A.B两台服务器,用户在第一次访问网站时,N ...

  7. 16. 使用vue3结构及配置管理

    主要内容: vue-cli2和3的区别 创建vue-cli3脚手架 vue-cli3项目的目录结构 vue-cli2和vue-cli3中 main.js文件的区别 vue-cli3的配置文件管理 一. ...

  8. Python编程中可能经常用到的函数

    1.os.walk() 一般用法为 import os ph=r'D:\temp\build' for root,dirs,files in os.walk(ph): print(root,dirs, ...

  9. dk.exe自动填报程序的反编译

    dk.exe自动填报程序的反编译 dk.exe用于学校每日健康报的自动填写.

  10. Python中树的遍历-堆排序

    1.二叉树的遍历 遍历:迭代所有元素一遍. 树的遍历:对树中所有的元素不重复的访问一遍,也成扫描 广度优先遍历:层序遍历 深度优先遍历:前序.中序.后续遍历. 遍历序列:将树中所有元素遍历一遍后,得到 ...