[C#] 使用 NAudio 实现音频可视化
预览:
最初版本:
更新:
再次更新 (这次优化了算法):
[演示] C# NAudio Direct2D 音频可视化
捕捉声卡输出:
实现音频可视化, 第一步就是获得音频采样, 这里我们选择使用计算机正在播放的音频作为采样源进行处理:
NAudio 中, 可以借助 WasapiLoopbackCapture 来进行捕捉:
WasapiLoopbackCapture cap = new WasapiLoopbackCapture();
cap.DataAvailable += (sender, e) => // 录制数据可用时触发此事件, 参数中包含音频数据
{float[] allSamples = Enumerable // 提取数据中的采样.Range(0, e.BytesRecorded / 4) // 除以四是因为, 缓冲区内每 4 个字节构成一个浮点数, 一个浮点数是一个采样.Select(i => BitConverter.ToSingle(e.Buffer, i * 4)) // 转换为 float.ToArray(); // 转换为数组// 获取采样后, 在这里进行详细处理
}
cap.StartRecording(); // 开始录制
分离左右通道:
获取完采样后, 我们还需要对采样进行一点小处理, 因为捕获的数据是分通道的, 一般是左右声道:
// 设定我们已经将刚刚的采样保存到了变量 AllSamples 中, 类型为 float[]
int channelCount = cap.WaveFormat.Channels; // WasapiLoopbackCapture 的 WaveFormat 指定了当前声音的波形格式, 其中包含就通道数
float[][] channelSamples = Enumerable.Range(0, channelCount).Select(channel => Enumerable.Range(0, AllSamples.Length / channelCount).Select(i => AllSamples[channel + i * channelCount]).ToArray()).ToArray();
取通道平均值
将采样分为一个个通道的采样后, 我们可以将其合并, 取平均值, 以便于绘制:
// 设定我们已经将分开了的采样保存到了变量 ChannelSamples 中, 类型为 float[][]
// 例如通道数为2, 那么左声道的采样为 ChannelSamples[0], 右声道为 ChannelSamples[1]
float[] averageSamples = Enumerable.Range(0, AllSamples.Length / channelCount).Select(index => Enumerable.Range(0, channelCount).Select(channel => ChannelSamples[channel][index]).Average()).ToArray();
绘制时域图象:
处理刚刚的采样后, 你可以直接将其作为数据绘制到窗口中, 这即是时域图象, 这里使用最简单的折线绘制.
// 设定 g 为窗口的 Graphics 对象, windowHeight 为窗口的显示区域高度
// 设定通道采样平均值为 AverageSamples, 类型为 float[]
Point[] points = AverageSamples.Select((v, i) => new Point(i, windowHeight - v)).ToArray(); // 将数据转换为一个个的坐标点
g.DrawLines(Pens.Black, points); // 连接这些点, 画线
傅里叶变换:
NAudio 中还提供了快速傅里叶变换的方法, 通过傅里叶变换, 可以将时域数据转换为频域数据, 也就是我们所说的频谱
// 我们将对 AverageSamples 进行傅里叶变换, 得到一个复数数组// 因为对于快速傅里叶变换算法, 需要数据长度为 2 的 n 次方, 这里进行
float log = Math.Ceiling(Math.Log(AverageSamples.Length, 2)); // 取对数并向上取整
int newLen = (int)Math.Pow(2, log); // 计算新长度
float[] filledSamples = new float[newLen];
Array.Copy(AverageSamples, filledSamples, AverageSamples.Length); // 拷贝到新数组
Complex[] complexSrc = filledSamples.Select(v => new Complex(){ X = v }) // 将采样转换为复数.ToArray();
FastFourierTransform(false, log, complexSrc); // 进行傅里叶变换// 变换之后, complexSrc 则已经被处理过, 其中存储了频域信息
分析频域信息:
对于傅里叶变换的频域信息, 需要稍加处理才可以方便的使用, 首先是提取有用的信息:
// NAudio 的傅里叶变换结果中, 似乎不存在直流分量(这使我们的处理更加方便了), 但它也是有共轭什么的(也就是数据左右对称, 只有一半是有用的)
// 仍然使用刚刚的 complexSrc 作为变换结果, 它的类型是 Complex[]Complex[] halfData = complexSrc.Take(complexSrc.Length / 2).ToArray(); // 一半的数据
float[] dftData = halfData.Select(v => Math.Sqrt(v.X * v.X + v.Y * v.Y)) // 取复数的模.ToArray(); // 将复数结果转换为我们所需要的频率幅度// 其实, 到这里你完全可以把这些数据绘制到窗口上, 这已经算是频域图象了, 但是对于音乐可视化来讲, 某些频率的数据我们完全不需要
// 例如 10000Hz 的频率, 我们完全没必要去绘制它, 取 最小频率 ~ 2500Hz 足矣
// 对于变换结果, 每两个数据之间所差的频率计算公式为 采样率/采样数, 那么我们要取的个数也可以由 2500 / (采样率 / 采样数) 来得出
int count = 2500 / (cap.WaveFormat.SampleRate / filledSamples.Length);
float[] finalData = dftData.Take(count).ToArray();
绘制频域图象:
得到上面分析后的 finalData 后, 我们就可以直接绘制出来了, 这次使用柔和的曲线绘制
// 设定 g 为窗口的 Graphics 对象, height 为窗口高度
PointF[] points = finalData.Select((v, i) => new PointF(i, height - v)).ToArray();
g.DrawCurve(Pens.Purple, points); // Graphics 可以直接绘制曲线
更优的绘制:
上面的时域和频域图象, 我们都是一股脑的将数据的索引作为 X 坐标, 窗口高度减去数据值作为 Y 坐标, 有两个突出的问题:
- 数据可能无法填满窗口的宽度或者超出窗口的宽度范围
- 数据太大时, 也会导致绘制的线条超出窗口高度
第一个问题好解决, 直接使索引所占数据长度的百分比恰好等于 X 坐标相对于窗口宽度的百分比即可:
x=index÷dataLength∗windowWidthx = index \div dataLength * windowWidth x=index÷dataLength∗windowWidth
对于第二个问题, 有两个解决方案, 一是直接为数据加权重, 例如统一乘 0.5, 使数据减小一节, 二就是套一个函数, 例如 log 函数, 毕竟 log 函数在较高自变量的情况下, 因变量的变化趋势越来越小, 我们只需要对这个 log 函数进行稍加处理, 就可以直接应用到数据变换数据上, 使其不超出窗口绘图区域
另外, 我们也可以平滑频谱显示(指动画变换), 它的原理大概是这样:
例如这次进行傅里叶变换的结果是:
{0, 100, 50}
,下一次傅里叶变换的结果是:
{100, 0, 0}
,可以得出, 增量为:
{100, -100, -50}
,在更新变换结果时, 我们不再直接将新的结果替换旧的结果, 而是在旧的结果的基础上, 加上增量×权重
例如权重是
0.5
时, 那么实际增量是:{50, -50, -25}
,那么实际新的值是:
{50, 50, 25}
,如果下一次变换的结果还是
{100, 0, 0}
, 那我们再次从{50, 50, 25}
向新值逼近, 权重仍然是0.5
, 那么实际增量是:{25, -25, -12.5}
,注意到了吗? 这次的增量是上次增量的一半, 这正好是一个减速运动, 而且新值与旧值的差越大, 变化的就越快, 而它们会不断重合, 因而速度不断变慢, 形成减速运动的频谱图.
更多内容:
更多关于 NAudio 的使用, 可以看这篇文章: [C#] NAudio 的各种常见使用方式 播放 录制 转码 音频可视化
项目已开源:
关于本文章涉及的大部分内容, 均在 github.com/SlimeNull/AudioTest 仓库中的 Null.AudioVisualizer 项目中有写. (注释妥当了)
如果是之后更新过的, 优化了算法的,可以在 github.com/SlimeNull/AudioVisualizer 仓库查看源代码. (注释妥当了)
其实音频可视化我老早就想做了, 但是本人数学不是非常的好, 不过最后总算是坚持下来了, 弄出来了啊, 心情老激动了
[C#] 使用 NAudio 实现音频可视化相关推荐
- android pcm频谱_Android音频可视化
本文作者:熊鋆洋 (网易云音乐大前端团队) 前言 音频可视化,顾名思义就是将声音以视觉的方式呈现出来.如何将音频信号绘制出来?如何将声音的变化在视觉上清晰的表现出来,让视觉和听觉上的感受一致?这些在 ...
- 傅里叶变换音频可视化_Web Audio在音频可视化中的应用
Web Audio在音频可视化中的应用 本文有两个关键词:音频可视化和Web Audio.前者是实践,后者是其背后的技术支持. Web Audio 是很大的知识点,本文会将重点放在如何获取音频数据这块 ...
- 傅里叶变换音频可视化_快速上手网易云音乐可视化
作者:Sanpeier https://juejin.im/post/5dd88289e51d4523564243da 什么是音频可视化 音频可视化,顾名思义,就是通过获取音频的波形.频率和其他来自音 ...
- 傅里叶变换音频可视化_HTML5如何实现音频可视化频谱跳动
本篇教程探讨了HTML5如何实现音频可视化频谱跳动,希望阅读本篇文章以后大家有所收获,帮助大家HTML5+CSS3从入门到精通 . < html> HTML5音频可视化频谱跳动代码 * { ...
- html频谱跳动效果,HTML5音频可视化频谱跳动代码
HTML5音频可视化频谱跳动代码 *{ margin:0; padding:0; } #canvas { display: block; background: linear-gradient(135 ...
- 前端如何查看音频的长度_Android音频可视化
本文作者:熊鋆洋 (网易云音乐大前端团队) 前言 音频可视化,顾名思义就是将声音以视觉的方式呈现出来.如何将音频信号绘制出来?如何将声音的变化在视觉上清晰的表现出来,让视觉和听觉上的感受一致?这些在 ...
- 傅里叶变换音频可视化_H5录音音频可视化-实时波形频谱绘制、频率直方图
这段时间给GitHub Recorder开源库添加了两个新的音频可视化功能,比以前单一的动态波形显示丰富了好多(下图后两行是不是比第一行看起来丰满些):趁热打铁写了一个音频可视化相关扩展测试代码,下面 ...
- 20X01 FCPX插件 音频可视化视觉特效 PFS Fcpx Audio Effector
20X01 FCPX插件 音频可视化视觉特效 PFS Fcpx Audio Effector [插件简介] FCPX 音频可视化工具 FCPX: Audio Effector 可直接在FCPX软件中 ...
- Unity 进阶 之 实现简单的音频可视化封装(包括音频和麦克风)
Unity 进阶 之 实现简单的音频可视化封装(包括音频和麦克 目录 Unity 进阶 之 实现简单的音频可视化封装(包括音频和麦克 一.简单介绍 二.实现原理 三.注意事项 四.效果预览 五.实现步 ...
最新文章
- 2017年高级二级计算机考试试题,2017年计算机二级高级Office考试试题操作题
- 2019年Vue学习路线图
- hdu4821 字符串hash
- html怎么引入sass样式,[样式设置] 使用sass格式的方式
- C# 文件操作之创建文件夹
- javascript高程3 学习笔记(二)
- 举例讲清楚模型树和回归树的区别
- java 8 新功能详解_Java 8的8个新功能
- datatable使用_使用Streamlit从简单的Python脚本创建交互式WebApp
- DeVOpS 实战:Kubernetes 微服务监控体系
- html5 vr效果,HTML5 Three.js 虚拟现实小实验(VR Experiment)
- 浪潮服务器5112面板灯_SA5112M4 – IPMI设置
- 54 MM配置-评估和科目设置-将评估分为群集分组
- httpserver
- mysql运维技巧_​mysql初级运维使用技巧
- [转载] Unicode文本排序和Unicode数据库
- JavaScript中的面向对象--对象创建
- Flex 得到一个对象的所有属性
- catia2017安装包打开没反应_CATIA V5-6R2017软件下载与安装教程
- Cocos2D场景编辑器
热门文章
- 华为防火墙企业双出口专线,配置策略路由实现多个ISP出接口的智能选路和双向NAT
- 相关性分析热点图_防老剂TMQ及6PPD价格上涨逻辑分析及后市展望
- 构造方法 梯形面积
- 【题解】A1004 求三个数的平均数
- FFmpeg提取视频中的音频
- python语言是网络编程语言吗_三大主流编程语言Python为啥这么牛?
- python两个表格相同数据筛选_python如何统计所有文本文件的行数 如何用python实现两个文件重复数据筛选并统计...
- 安卓开发快速学习!330页PDF,10万字的知识点总结,附面试题答案
- 学习SQL的侦探游戏
- 前端三剑客 HTML、CSS、JavaScript 入门到上手