C#+Audio绘制麦克风波形
/*********************************************************
C#+Audio绘制麦克风波形
By:wgscd
Date:2016-8-1
*********************************************************/
<Window
x:Class="MMWeather.MicroWaveWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MMWeather"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="音量圆圈"
Width="894"
Height="288"
mc:Ignorable="d">
<Grid>
<Canvas x:Name="canvas" Background="#111111" />
</Grid>
</Window>
using NAudio.Wave;
using NAudio.Dsp;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Shapes;
namespace MMWeather
{
/*********************************************************
C#+Audio绘制麦克风波形
By:wgscd
Date:2016-8-1
*********************************************************/
public partial class MicroWaveWindow : Window
{
private WaveInEvent waveIn;
private const int BarCount = 15;
private Rectangle[] bars;
// FFT 相关
private Complex[] fftBuffer;
private int fftSize = 1024;
private int sampleCount = 0;
private float[] sampleBuffer;
// 平滑参数
private float[] previousHeights;
private Brush[] barBrushes;
public MicroWaveWindow()
{
InitializeComponent();
InitAudioVisualizer();
}
private void InitAudioVisualizer()
{
bars = new Rectangle[BarCount];
previousHeights = new float[BarCount];
barBrushes = new Brush[BarCount];
// 预计算颜色渐变 - 从蓝色到紫色到红色
for (int i = 0; i < BarCount; i++)
{
float t = (float)i / BarCount;
Color color = ColorFromHSV(220 - t * 200, 0.9, 0.4 + t * 0.5);
barBrushes[i] = new SolidColorBrush(color);
}
// 创建柱状图
for (int i = 0; i < BarCount; i++)
{
var bar = new Rectangle
{
Width = 8,
Fill = barBrushes[i],
RadiusX = 3,
RadiusY = 3
};
Canvas.SetLeft(bar, i * 10 + 5);
canvas.Children.Add(bar);
bars[i] = bar;
}
// 初始化 FFT 缓冲区
fftBuffer = new Complex[fftSize];
sampleBuffer = new float[fftSize];
// 初始化音频输入
waveIn = new WaveInEvent
{
WaveFormat = new WaveFormat(44100, 16, 1),
DeviceNumber = 0 // 默认麦克风
};
waveIn.DataAvailable += WaveIn_DataAvailable;
waveIn.RecordingStopped += WaveIn_RecordingStopped;
waveIn.StartRecording();
// 窗口关闭时停止录音
this.Closed += (s, e) =>
{
waveIn?.StopRecording();
waveIn?.Dispose();
};
}
private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
{
// 将音频数据转换为 float 并填充到缓冲区
for (int i = 0; i < e.BytesRecorded / 2 && sampleCount < fftSize; i++)
{
short sample = BitConverter.ToInt16(e.Buffer, i * 2);
sampleBuffer[sampleCount] = sample / 32768f;
sampleCount++;
}
// 当收集够 FFT 所需的样本数时进行处理
if (sampleCount >= fftSize)
{
ProcessFFT();
sampleCount = 0;
}
}
private void WaveIn_RecordingStopped(object sender, StoppedEventArgs e)
{
// 录音停止时的处理
}
private void ProcessFFT()
{
// 准备 FFT 输入 - 应用汉宁窗
for (int i = 0; i < fftSize; i++)
{
double window = 0.5 * (1 - Math.Cos(2 * Math.PI * i / (fftSize - 1)));
fftBuffer[i].X = sampleBuffer[i] * (float)window;
fftBuffer[i].Y = 0;
}
// ===== 修复:正确计算 FFT 阶数 =====
int fftOrder = (int)Math.Log(fftSize, 2);
FastFourierTransform.FFT(true, fftOrder, fftBuffer);
// 提取频谱幅度
float[] magnitudes = new float[fftSize / 2];
float maxMag = 0;
for (int i = 0; i < fftSize / 2; i++)
{
magnitudes[i] = (float)Math.Sqrt(fftBuffer[i].X * fftBuffer[i].X + fftBuffer[i].Y * fftBuffer[i].Y);
if (magnitudes[i] > maxMag) maxMag = magnitudes[i];
}
// 防止除零
if (maxMag < 0.0001f) maxMag = 0.0001f;
// 映射到柱状图
float[] barValues = new float[BarCount];
int binsPerBar = (fftSize / 2) / BarCount;
for (int i = 0; i < BarCount; i++)
{
float sum = 0;
int startBin = i * binsPerBar;
int endBin = Math.Min(startBin + binsPerBar, fftSize / 2);
for (int j = startBin; j < endBin; j++)
{
sum += magnitudes[j];
}
float avg = sum / (endBin - startBin);
// ===== 振幅映射优化 =====
// 归一化
float normalized = avg / maxMag;
// 对数压缩(压低底噪,放大峰值)
float logValue = (float)(Math.Log(1 + normalized * 300) / Math.Log(301));
// 平方处理,让峰值更突出
logValue = logValue * logValue * 1.2f;
// 动态范围拉伸(切除底部噪音)
logValue = (logValue - 0.03f) / 0.97f;
if (logValue < 0) logValue = 0;
barValues[i] = Math.Min(logValue, 1f);
}
// UI 更新
Dispatcher.Invoke(() =>
{
for (int i = 0; i < BarCount; i++)
{
// 平滑处理 - 平衡响应速度和流畅度
float smoothed = previousHeights[i] * 0.45f + barValues[i] * 0.55f;
previousHeights[i] = smoothed;
// 计算高度(最大 280px)
double height = smoothed * 80;
height = Math.Max(height, 2); // 最小高度保证可见
bars[i].Height = height;
Canvas.SetTop(bars[i], (canvas.ActualHeight - height) / 2);
// 根据高度动态调整透明度
bars[i].Opacity = 0.5 + smoothed * 0.5;
}
});
}
// HSV 转 Color 辅助方法
private Color ColorFromHSV(double hue, double saturation, double value)
{
int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
double f = hue / 60 - Math.Floor(hue / 60);
double v = value;
double p = value * (1 - saturation);
double q = value * (1 - f * saturation);
double t = value * (1 - (1 - f) * saturation);
double r = 0, g = 0, b = 0;
switch (hi)
{
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
return Color.FromArgb(255, (byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
}
protected override void OnClosed(EventArgs e)
{
waveIn?.StopRecording();
waveIn?.Dispose();
base.OnClosed(e);
}
}
}
fffffffffffffffff
test red font.

浙公网安备 33010602011771号