由于项目需要音频波形显示,所以仿照 Cool Edit 实现一个简易的音频播放器。
效果图
*(空格键播放选中区域)
核心代码
using System;
using System.Drawing;
using System.Windows.Forms;
namespace JAudioWave
{
public partial class JAudioWave : UserControl
{
private Pen gridPen;
private Pen topPeakPen;
private Pen bottomPeakPen;
private MaxPeakProvider _maxPeakProvider;
private WaveAudioRendererSettings _settings;
private bool isMouseDown = false;
private bool isStartMove = false;
private int moveStartX = 0; // 每一次滑动蕲艾
private int gStartX = 0; // 音频区域起点
private int gEndX = 0; // 音频区域终点
private int direction = 0; // 0:反方向 1:正方向
public Action<int, int> OnSelectedAudioArea;
public JAudioWave()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);// 不闪烁 支持双缓存
InitializeComponent();
init();
}
private void init()
{
initPen();
TopHeight = 150;
BottomHeight = 150;
_maxPeakProvider = new MaxPeakProvider();
}
private void initPen()
{
gridPen = new Pen(Color.FromArgb(18, 59, 40));
topPeakPen = new Pen(Color.FromArgb(75, 243, 167));
bottomPeakPen = new Pen(Color.FromArgb(75, 243, 167));
}
private WaveAudioRendererSettings getSettings()
{
if (_settings != null)
return _settings;
_settings = new WaveAudioRendererSettings();
_settings.TopHeight = TopHeight;
_settings.BottomHeight = BottomHeight;
_settings.Width = this.Width;
_settings.TopPeakPen = topPeakPen;
_settings.BottomPeakPen = bottomPeakPen;
return _settings;
}
protected override void OnPaint(PaintEventArgs e)
{
Console.WriteLine("OnPaint");
base.OnPaint(e);
draw(e.Graphics);
//_maxPeakProvider.ResetPeakInfoStatus();
}
private void draw(Graphics g)
{
// 背景
WaveAudioRendererSettings settings = getSettings();
g.FillRectangle(settings.BackgroundBrush, 0, 0, this.Width, this.Height);
// 网格
drawGraid(g, this.Width, this.Height);
// 波形
var midPoint = settings.TopHeight;
int x = 0;
WavePeakInfo currentPeak = _maxPeakProvider.GetPeakInfo(x);
if (currentPeak == null)
return;
while (x < settings.Width)
{
WavePeakInfo nextPeak = _maxPeakProvider.GetPeakInfo(x);
for (int n = 0; n < settings.PixelsPerPeak; n++)
{
float lineHeight = settings.TopHeight * currentPeak.Max;
g.DrawLine(settings.TopPeakPen, x, midPoint, x, midPoint - lineHeight);
lineHeight = settings.BottomHeight * currentPeak.Min;
g.DrawLine(settings.BottomPeakPen, x, midPoint, x, midPoint - lineHeight);
x++;
}
for (int n = 0; n < settings.SpacerPixels; n++)
{
float max = Math.Min(currentPeak.Max, nextPeak.Max);
float min = Math.Max(currentPeak.Min, nextPeak.Min);
float lineHeight = settings.TopHeight * max;
g.DrawLine(settings.TopSpacerPen, x, midPoint, x, midPoint - lineHeight);
lineHeight = settings.BottomHeight * min;
g.DrawLine(settings.BottomSpacerPen, x, midPoint, x, midPoint - lineHeight);
x++;
}
currentPeak = nextPeak;
}
drawRect(g, gEndX - gStartX, this.Height, gStartX, gEndX);
}
public void drawGraid(Graphics g, int width, int height)
{
//垂直
for (int i = 0; i < width;)
{
g.DrawLine(gridPen, new Point(i, 0), new Point(i, width));
i += 45;
}
//水平
for (int j = 0; j < height;)
{
g.DrawLine(gridPen, new Point(0, j), new Point(width, j));
j += 45;
}
}
private void drawSelectRect(int startX, int endX)
{
Graphics g = Graphics.FromHwnd(this.Handle);
drawRect(g, endX - startX, this.Height, startX, endX);
}
private void drawRect(Graphics g, int width, int height, int startX, int endX)
{
if (direction == 0) // 反方向
{
if (startX < endX)
{
#region 反向
int diff = endX - startX;
WaveAudioRendererSettings settings = getSettings();
g.FillRectangle(settings.BackgroundBrush, startX, 0, diff, this.Height);
int num = startX / 45;
int a = startX % 45;
int start = a == 0 ? num * 45 : num * 45 + 45;
//Pen myPen = new Pen(Color.FromArgb(10, 215, 221, 224));
Pen myPen = new Pen(Color.DarkBlue);
//垂直
for (int i = start; i < endX;)
{
g.DrawLine(gridPen, new Point(i, 0), new Point(i, height));
i += 45;
}
if (diff > 0)
{
//水平
for (int j = 0; j < height;)
{
g.DrawLine(gridPen, new Point(startX, j), new Point(endX - 1, j));
j += 45;
}
}
//水平
//for (int j = 0; j < height;)
//{
// g.DrawLine(gridPen, new Point(startX, j), new Point(endX, j));
// j += 45;
//}
WavePeakInfo currentPeak = _maxPeakProvider.GetPeakInfo(startX);
if (currentPeak == null)
return;
var midPoint = settings.TopHeight;
for (int x = startX; x < endX; x++)
{
WavePeakInfo nextPeak = _maxPeakProvider.GetPeakInfo(x);
Pen _p = new Pen(Color.FromArgb(18, 59, 40));
if (nextPeak.IsSelected)
{
_p = topPeakPen;
}
else
{
_p = new Pen(Color.FromArgb(18, 59, 40));
}
var lineHeight = settings.TopHeight * currentPeak.Max;
g.DrawLine(topPeakPen, x, midPoint, x, midPoint - lineHeight);
lineHeight = settings.BottomHeight * currentPeak.Min;
g.DrawLine(topPeakPen, x, midPoint, x, midPoint - lineHeight);
//_maxPeakProvider.UpdatePeakInfoStatus(x);
currentPeak = nextPeak;
}
#endregion
}
else
{
int diff = startX - endX;
Rectangle rect = new Rectangle();
rect.Width = diff;
rect.Height = height;
rect.Location = new Point(endX, 0);
SolidBrush mysbrush1 = new SolidBrush(Color.FromArgb(180, 238, 238, 247));
g.FillRectangle(mysbrush1, rect);
}
}
else // 正方向
{
if (startX > endX)
{
#region 反向
int diff = startX - endX;
WaveAudioRendererSettings settings = getSettings();
g.FillRectangle(settings.BackgroundBrush, endX, 0, diff, this.Height);
int num = endX / 45;
int a = endX % 45;
int start = a == 0 ? num * 45 : num * 45 + 45;
Pen myPen = new Pen(Color.DarkBlue);
//垂直
for (int i = start; i < startX;)
{
g.DrawLine(gridPen, new Point(i, 0), new Point(i, height));
i += 45;
}
if (diff > 0)
{
//水平
for (int j = 0; j < height;)
{
g.DrawLine(gridPen, new Point(endX, j), new Point(startX - 1, j));
j += 45;
}
}
WavePeakInfo currentPeak = _maxPeakProvider.GetPeakInfo(endX);
if (currentPeak == null)
return;
var midPoint = settings.TopHeight;
for (int x = endX; x < startX; x++)
{
WavePeakInfo nextPeak = _maxPeakProvider.GetPeakInfo(x);
Pen _p = new Pen(Color.FromArgb(18, 59, 40));
if (nextPeak.IsSelected)
{
_p = topPeakPen;
}
else
{
_p = new Pen(Color.FromArgb(18, 59, 40));
}
var lineHeight = settings.TopHeight * currentPeak.Max;
g.DrawLine(topPeakPen, x, midPoint, x, midPoint - lineHeight);
lineHeight = settings.BottomHeight * currentPeak.Min;
g.DrawLine(topPeakPen, x, midPoint, x, midPoint - lineHeight);
//_maxPeakProvider.UpdatePeakInfoStatus(x);
currentPeak = nextPeak;
}
#endregion
}
else
{
int diff = endX - startX;
Rectangle rect = new Rectangle();
rect.Width = diff;
rect.Height = height;
rect.Location = new Point(startX, 0);
SolidBrush mysbrush1 = new SolidBrush(Color.FromArgb(180, 238, 238, 247));
g.FillRectangle(mysbrush1, rect);
}
}
}
public void refresh()
{
gStartX = 0;
gEndX = 0;
this.Refresh();
}
public int TopHeight { get; set; }
public int BottomHeight { get; set; }
public int Bit { get; set; }
private void JAudioWave_MouseDown(object sender, MouseEventArgs e)
{
refresh();
isMouseDown = true;
string x = e.X.ToString(); //x坐标
string y = e.Y.ToString(); //Y坐标
moveStartX = e.X;
gStartX = e.X;
isStartMove = true;
}
private void JAudioWave_MouseMove(object sender, MouseEventArgs e)
{
if (!isMouseDown)
return;
string x = e.X.ToString(); //x坐标
string y = e.Y.ToString(); //Y坐标
if (isStartMove)
{
direction = moveStartX > e.X ? 0 : 1;
isStartMove = false;
}
drawSelectRect(moveStartX, e.X);
moveStartX = e.X;
}
private void JAudioWave_MouseUp(object sender, MouseEventArgs e)
{
if (!isMouseDown)
return;
isMouseDown = false;
gEndX = e.X;
if (OnSelectedAudioArea != null && gStartX != gEndX)
{
int sx = gStartX;
int ex = gEndX;
if (direction == 0)
{
sx = gEndX;
ex = gStartX;
}
else
{
sx = gStartX;
ex = gEndX;
}
OnSelectedAudioArea.BeginInvoke(_maxPeakProvider.GetSize(sx), _maxPeakProvider.GetSize(ex), null, null);
}
}
private void JAudioWave_Load(object sender, EventArgs e)
{
}
// ============================ 对外接口 ===========================================
public void LoadAudio(string file, int bitsPerSample)
{
gStartX = 0;
gEndX = 0;
WaveAudioRendererSettings settings = getSettings();
_maxPeakProvider.Init(bitsPerSample, file);
_maxPeakProvider.Load(settings);
}
}
}
功能
- 加载音频,显示出音频波形
- 左右滑动鼠标选择区域,空格键播放选中区域音频。
TODO
- 来回滑动, 波形存在细微变动,暂未找到原因。
- 滑动过快, 波形容易造成短暂异常。
- 音频播放通过系统api接口简单实现(将选中区域保存本地,来进行播放),有待改善。
*** 以上1.2 问题, 如有大侠解决,望指导一二,不胜感激。
原版 cool edit 效果图
** 哪位大侠能实现这个效果,也希望能共享下。不胜感激。(可左右来回滑动来选择区域,而且不卡顿.)