实现思路
NAudio
是由 Mark Heath 编写的开源 .NET 音频库,提供丰富的音频操作功能,也提供了音频可视化的Demo。其基本思路如下:
- 计算样本和像素比例:
- 计算每个音频样本的字节数。
- 根据音频流的长度和样本字节数计算总样本数。
- 计算每个像素点对应的样本数,以及步长(峰值像素加上间隔像素)。
- 绘制波形图:
-
循环遍历整个宽度,使用
peakProvider
获取像素对应的音频的峰值。NAudio提供了四种高峰值计算策略:
1.Max_Absolute_Value
:最大绝对值,即从数据集中找出具有最大绝对值的数据点作为峰值。
2.Max_Rms_Value
:最大均方根值,即从数据集中找出具有最大均方根值的数据点作为峰值。
3.Sampled_Peaks
:采样峰值,可能是指在数据集中随机或按照某种规则选取一定数量的峰值进行计算。
4.Average
:平均值,即计算数据集中所有点的平均值作为峰值。 -
根据峰值和设置的高度计算音波上半部分和下半部分的线条高度,
- 创建
WaveformItem
对象,设置其宽度、位置、高度和颜色。 - 将
WaveformItem
对象添加到集合中。
- 创建
-
计算当前和下一个峰值的最大和最小值,绘制间隔条,同样将宽度、位置、高度和颜色记录到
WaveformItem
-
最后将
WaveformItem
的集合,绘制矩形到Canvas上,实现音波的可视化
-
代码Demo
- 获取峰值信息
public ObservableCollection<WaveformItem> Render(WaveStream waveStream, IPeakProvider peakProvider, WaveFormRendererSettings settings)
{
ObservableCollection<WaveformItem> waveformItems = new ObservableCollection<WaveformItem>();
// 计算每个样本占据的字节数
int bytesPerSample = (waveStream.WaveFormat.BitsPerSample / 8);
// 计算总的音频样本数
var samples = waveStream.Length / (bytesPerSample);
// 计算每个像素点对应的音频样本数
var samplesPerPixel = (int)(samples / settings.Width);
// 计算步长,包括峰值像素和间隔像素
var stepSize = settings.PixelsPerPeak + settings.SpacerPixels;
// 初始化peakProvider,根据音频流和计算的样本数目
peakProvider.Init(waveStream.ToSampleProvider(), samplesPerPixel * stepSize);
// 调用重载的 Render 方法进行实际渲染
waveformItems = Render(peakProvider, settings);
return waveformItems;
}
private static ObservableCollection<WaveformItem> Render(IPeakProvider peakProvider, WaveFormRendererSettings settings)
{
ObservableCollection<WaveformItem> waveformItems = new ObservableCollection<WaveformItem>();
// 如果设置为使用分贝缩放,则创建 DecibelPeakProvider 来处理峰值
if (settings.DecibelScale)
peakProvider = new DecibelPeakProvider(peakProvider, 48);
int x = 0;
var currentPeak = peakProvider.GetNextPeak();
var midPoint = settings.TopHeight;
int i = 0;
// 在整个宽度内循环绘制音波图
while (x < settings.Width)
{
// 获取下一个峰值
var nextPeak = peakProvider.GetNextPeak();
// 绘制峰值条
{
// 计算并绘制顶底部峰值条
var topLineHeight = settings.TopHeight * currentPeak.Max;
// 计算并绘制底部峰值条
var bottomLineHeight = settings.BottomHeight * currentPeak.Min;
var waveHeight = Math.Abs(topLineHeight + bottomLineHeight);
var waveformItem = new WaveformItem
{
Width = settings.PixelsPerPeak,
Left = i * (settings.PixelsPerPeak + settings.SpacerPixels),
Top =(settings.Height - waveHeight) /2,
Height = waveHeight,
Color = settings.WaveBrush
};
waveformItems.Add(waveformItem);
}
// 绘制间隔条
{
// 计算最大和最小峰值,并绘制顶部间隔条
var max = Math.Min(currentPeak.Max, nextPeak.Max);
var min = Math.Max(currentPeak.Min, nextPeak.Min);
// 绘制峰值条
// 计算并绘制顶底部峰值条
var topLineHeight = settings.TopHeight * currentPeak.Max;
// 计算并绘制底部峰值条
var bottomLineHeight = settings.BottomHeight * currentPeak.Min;
var waveHeight = Math.Abs(topLineHeight + bottomLineHeight);
var waveformItem = new WaveformItem
{
Width = settings.SpacerPixels,
Left = settings.PixelsPerPeak + i * (settings.PixelsPerPeak + settings.SpacerPixels),
Top = (settings.Height - waveHeight) / 2,
Height = waveHeight,
Color = settings.SpacerBrush
};
waveformItems.Add(waveformItem);
}
x += settings.PixelsPerPeak + settings.SpacerPixels;
i++;
currentPeak = nextPeak;
}
return waveformItems;
}
- 绘制矩形到Canvas
private void DrawRectangles()
{
if (canvas == null
|| WaveSetting.ItemsSource == null
|| WaveSetting.ItemsSource.Count == 0)
{
return;
}
canvas.Children.Clear();
canvas.Width = this.Width;
foreach (var item in WaveSetting.ItemsSource)
{
var rect = new System.Windows.Shapes.Rectangle
{
Width = item.Width,
Height = item.Height,
Fill = item.Color,
RadiusX = item.Width * 0.4, // 设置圆角的水平半径
RadiusY = item.Width * 0.4 // 设置圆角的垂直半径
};
Canvas.SetLeft(rect, item.Left);
Canvas.SetTop(rect, item.Top);
canvas.Children.Add(rect);
}
}
效果
项目参考
样式参考
代码地址
NitasDemo/02WaveDemo at main · Nita121388/NitasDemo (github.com)