C# 声音强度图绘制
采集PCM音频数据
音频原来自麦克风
音频源来自录音文件
处理PCM音频数据
将PCM数据进行强度值换算
private void UpdateVoice(double[] audio)
{
// 计算RMS值
double rms = Math.Sqrt(audio.Select(x => x * x).Average());
// 将RMS值转换为分贝值(以dBFS为单位)
double dbfs = 20 * Math.Log10(rms);
// 绘制数据
volumeView.SetVolumeData(dbfs);
volumeView.Refresh();
}
绘制时频图
采用自定义控件的方式来绘制时频图,核心代码如下:
/// <summary>
/// 声音强度图
/// </summary>
public class SoundTimeVolumeControl : Control
{
// 创建一个画笔
private Pen pen = new Pen(Brushes.Blue, 1);
public Brush TextBrush = new SolidColorBrush(Colors.Gainsboro);
public void Refresh()
{
this.InvalidateVisual();
}
/// <summary>
/// 最大值
/// </summary>
private double mMaxVolume;
/// <summary>
/// 最小值
/// </summary>
private double mMinVolume;
// 数据源,用于存储折线图的数据
double[] dbList = new double[256];
public void SetVolumeData(double volume)
{
mMaxVolume = double.MinValue;
mMinVolume = double.MaxValue;
for (int i = dbList.Length - 1; i > 0; i--)
{
dbList[i] = dbList[i - 1];
mMaxVolume = Math.Max(dbList[i], mMaxVolume);
mMinVolume = Math.Min(dbList[i], mMinVolume);
}
dbList[0] = volume;
mMaxVolume = Math.Max(volume, mMaxVolume);
mMinVolume = Math.Min(volume, mMinVolume);
}
protected override void OnRender(DrawingContext drawingContext)
{
double width = this.ActualWidth;
double height = this.ActualHeight;
var itemWidth = width / 4;
var itemHeight = height / 3;
drawingContext.DrawRectangle(this.Background, null, new Rect(0, 0, width, height));
// 画方框
drawingContext.DrawRect(this.Foreground, 1, 1, width - 1, height - 1);
画竖线
//for (int i = 1; i < 4; i++)
//{
// var left = i * itemWidth;
// drawingContext.DrawLine(this.Foreground, left, 0, left, height);
//}
// 画横线
for (int i = 1; i < 3; i++)
{
var top = i * itemHeight;
drawingContext.DrawLine(this.Foreground, 0, top, 3, top);
drawingContext.DrawText(string.Format("-{0}dBFS", i * 30), this.TextBrush, 3 + 20, top, 13);
}
// 画折线
if (mMaxVolume > mMinVolume)
{
DrawPointPath(drawingContext, width, height);
}
}
private void DrawPointPath(DrawingContext drawingContext, double width, double height)
{
var itemHeight = height / 20;
var itemWidth = width / dbList.Length;
var amplitude = Math.Max(mMaxVolume - mMinVolume, 60);
// 开始绘制路径
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext context = geometry.Open())
{
// 将路径移动到第一个数据点
var x = (1 - (dbList[0] - mMinVolume) / amplitude) * 18;
context.BeginFigure(new Point(0, x * itemHeight), false, false);
// 添加线段连接每个数据点
for (int i = 1; i < dbList.Length; i++)
{
if (dbList[i] < 0)
{
x = (1 - (dbList[i] - mMinVolume) / amplitude) * 18;
context.LineTo(new Point(i * itemWidth, x * itemHeight), true, false);
}
}
}
// 绘制路径
drawingContext.DrawGeometry(null, pen, geometry);
}
}