C# 实时声音频率图绘制
C# 实时声音频率图绘制
采集PCM音频数据
音频原来自麦克风
音频源来自录音文件
处理PCM音频数据
使用 FftSharp.FFT 将PCM数据进行傅里叶变换
安装FftSharp框架
在Nuget包管理器中搜索FftSharp并安装
傅里叶变换
将采集到的PCM数据进行傅里叶变换
// 傅里叶变换
System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(audio);
double[] ys = FftSharp.FFT.Magnitude(spectrum);
绘制频率图
采用自定义控件的方式来绘制频率图,核心代码如下:
protected override void OnRender(DrawingContext drawingContext)
{
// 渲染数据
DrawFrequency(drawingContext, this.ActualWidth, this.ActualHeight);
}
private void DrawFrequency(DrawingContext drawingContext, double imageWidth, double imageHeight)
{
double width = imageWidth - MarginLeft - 1;
double height = imageHeight - MarginBottom - 1;
var itemWidth = width / 4;
var itemHeight = height / 4;
drawingContext.DrawRectangle(this.Background, null, new Rect(0, 0, imageWidth, imageHeight));
// 画方框
drawingContext.DrawRect(this.Foreground, 1 + MarginLeft, 1, width, height);
// 画竖线
for (int i = 1; i < 4; i++)
{
var left = i * itemWidth;
drawingContext.DrawLine(this.Foreground, left + MarginLeft, 0, left + MarginLeft, height);
// 画文本
var freq = MaxFrequency * (i * 0.25);
drawingContext.DrawText(FormatUtil.Frequency(freq), this.TextBrush, left + MarginLeft, height + MarginBottom / 2 + 2, 13);
}
drawingContext.DrawText(FormatUtil.Frequency(0), this.TextBrush, MarginLeft + 10, height + MarginBottom / 2 + 2, 13);
drawingContext.DrawText(FormatUtil.Frequency(MaxFrequency), this.TextBrush, width + MarginLeft - 16, height + MarginBottom / 2 + 2, 13);
// 画横线
for (int i = 1; i 0)
{
DrawPointPath(drawingContext, width, height);
}
}
private void DrawPointPath(DrawingContext drawingContext, double width, double height)
{
var itemHeight = height / 20;
var itemWidth = width / dataPoints.Count;
var amplitude = (mMaxVolume - mMinVolume) * 2;
// 开始绘制路径
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext context = geometry.Open())
{
// 将路径移动到第一个数据点
var x = (1 - dataPoints[0] / amplitude) * 20;
context.BeginFigure(new Point(MarginLeft + 1, x * itemHeight), false, false);
// 添加线段连接每个数据点
for (int i = 1; i < dataPoints.Count; i++)
{
x = (1 - dataPoints[i] / amplitude) * 20;
context.LineTo(new Point(i * itemWidth + MarginLeft + 1, x * itemHeight), true, false);
}
}
// 绘制路径
drawingContext.DrawGeometry(null, pen, geometry);
}
其他拓展类
FormatUtil
internal class FormatUtil
{
public static string Frequency(double freq)
{
if (freq < 1000)
{
return string.Format("{0}Hz", (int)freq);
}
else
{
var value = Math.Floor(freq / 1000);
return string.Format("{0}kHz", value);
}
}
}
DrawingContextExt
public static class DrawingContextExt
{
public static void DrawRect(this DrawingContext drawingContext, Brush color, double x, double y, double w, double h)
{
drawingContext.DrawRectangle(null, new Pen(color, 1), new System.Windows.Rect(x, y, w, h));
}
public static void DrawLine(this DrawingContext drawingContext, Brush color, double x, double y, double x2, double y2)
{
drawingContext.DrawLine(new Pen(color, 1), new Point(x, y), new Point(x2, y2));
}
public static void FillEllipse(this DrawingContext drawingContext, Brush brush, double x, double y, double w, double h)
{
var radiusX = w / 2;
var radiusY = h / 2;
drawingContext.DrawEllipse(brush, null, new Point(x - radiusX, y - radiusY), radiusX, radiusY);
}
public static void DrawText(this DrawingContext drawingContext, string data, Brush brush, double x, double y, double emSize = 10)
{
// 创建FormattedText对象以设置文字的样式、位置和对齐方式
FormattedText formattedText = new FormattedText(
data,
System.Globalization.CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
emSize, brush);
// 设置文字在 (50, 50) 的位置水平和垂直居中
// 计算绘制点的坐标,使文本居中绘制
Point drawPoint = new Point(x - formattedText.Width / 2, y - formattedText.Height / 2);
// 绘制文字
drawingContext.DrawText(formattedText, drawPoint);
}
}