2016-09-20 162 views
1

我正在尝试提取wav文件的音频内容并将结果波形导出为图像(bmp/jpg/png)。如何将音频波形绘制成位图

所以我发现下面的代码绘制一个正弦波,按预期工作:

string filename = @"C:\0\test.bmp"; 
    int width = 640; 
    int height = 480; 
    Bitmap b = new Bitmap(width, height); 

    for (int i = 0; i < width; i++) 
    { 
     int y = (int)((Math.Sin((double)i * 2.0 * Math.PI/width) + 1.0) * (height - 1)/2.0); 
     b.SetPixel(i, y, Color.Black); 
    } 
    b.Save(filename); 

可正常工作完全,我希望做的是更换

int y = (int)((Math.Sin((double)i * 2.0 * Math.PI/width) + 1.0) * (height - 1)/2.0); 

与像

int y = converted and scaled float from monoWaveFileFloatValues 

那么,我会如何最好地去做这个以最简单的方式p ossible?

我有2个基本的问题,我需要处理(我认为)

  1. 转换浮在不松动的信息的方式来诠释,这是由于SetPixel(i, y, Color.Black);其中x & y都为诠释
  2. 样品跳过在x轴,使波形配合到限定空间audio length/image width得到的样本数由单个像素

另以平均强度在其将被表示选项是找到不依赖于上述方法的绘制波形的另一种方法。 Using a chart可能是一个好方法,但我希望能够直接渲染图像

这是所有要从控制台应用程序运行,我已经在一个浮动的音频数据(减去标题)阵列。


更新1

下面的代码使我得出使用System.Windows.Forms.DataVisualization.Charting所需的输出,但它花费了大约30秒,处理27776个样本,同时它做什么,我需要的,它是太很慢很有用。所以我仍然在寻找能够直接绘制位图的解决方案。如下图所示

System.Windows.Forms.DataVisualization.Charting.Chart chart = new System.Windows.Forms.DataVisualization.Charting.Chart(); 
    chart.Size = new System.Drawing.Size(640, 320); 
    chart.ChartAreas.Add("ChartArea1"); 
    chart.Legends.Add("legend1"); 

    // Plot {sin(x), 0, 2pi} 
    chart.Series.Add("sin"); 
    chart.Series["sin"].LegendText = args[0]; 
    chart.Series["sin"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline; 

    //for (double x = 0; x < 2 * Math.PI; x += 0.01) 
    for (int x = 0; x < audioDataLength; x ++) 
    { 
     //chart.Series["sin"].Points.AddXY(x, Math.Sin(x)); 
     chart.Series["sin"].Points.AddXY(x, leftChannel[x]); 
    } 

    // Save sin_0_2pi.png image file 
    chart.SaveImage(@"c:\tmp\example.png", System.Drawing.Imaging.ImageFormat.Png); 

输出: enter image description here

+0

所以你有任何代码读取音频文件?删除标题,然后查看数据?这应该是你的开始;只有在绘图之后..而且,不,对于绘制图表的点数并不是真的这么好,imo – TaW

+0

@TaW - “我已经将音频数据(除了标题)放在浮点数组中。 “所以我正在寻找下一步。 – Majickal

回答

2

所以我设法使用一个代码示例found here弄明白,尽管我做出我与它进行交互的方式一些细微的变化。

public static Bitmap DrawNormalizedAudio(List<float> data, Color foreColor, Color backColor, Size imageSize, string imageFilename) 
{ 
    Bitmap bmp = new Bitmap(imageSize.Width, imageSize.Height); 

    int BORDER_WIDTH = 0; 
    float width = bmp.Width - (2 * BORDER_WIDTH); 
    float height = bmp.Height - (2 * BORDER_WIDTH); 

    using (Graphics g = Graphics.FromImage(bmp)) 
    { 
     g.Clear(backColor); 
     Pen pen = new Pen(foreColor); 
     float size = data.Count; 
     for (float iPixel = 0; iPixel < width; iPixel += 1) 
     { 
      // determine start and end points within WAV 
      int start = (int)(iPixel * (size/width)); 
      int end = (int)((iPixel + 1) * (size/width)); 
      if (end > data.Count) 
       end = data.Count; 

      float posAvg, negAvg; 
      averages(data, start, end, out posAvg, out negAvg); 

      float yMax = BORDER_WIDTH + height - ((posAvg + 1) * .5f * height); 
      float yMin = BORDER_WIDTH + height - ((negAvg + 1) * .5f * height); 

      g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax, iPixel + BORDER_WIDTH, yMin); 
     } 
    } 
    bmp.Save(imageFilename); 
    bmp.Dispose(); 
    return null; 
} 


private static void averages(List<float> data, int startIndex, int endIndex, out float posAvg, out float negAvg) 
{ 
    posAvg = 0.0f; 
    negAvg = 0.0f; 

    int posCount = 0, negCount = 0; 

    for (int i = startIndex; i < endIndex; i++) 
    { 
     if (data[i] > 0) 
     { 
      posCount++; 
      posAvg += data[i]; 
     } 
     else 
     { 
      negCount++; 
      negAvg += data[i]; 
     } 
    } 

    if (posCount > 0) 
     posAvg /= posCount; 
    if (negCount > 0) 
     negAvg /= negCount; 
} 

为了得到它的工作,我不得不做一些事情打电话DrawNormalizedAudio你可以看到下面有什么,我需要做的方法之前:

Size imageSize = new Size(); 
    imageSize.Width = 1000; 
    imageSize.Height = 500; 
    List<float> lst = leftChannel.OfType<float>().ToList(); //change float array to float list - see link below 
    DrawNormalizedAudio(lst, Color.Red, Color.White, imageSize, @"c:\tmp\example2.png"); 

* change float array to float list

的结果如下,手拍波形样本的波形表示: enter image description here

我是qui确定需要对代码进行一些更新/修改,但这是一个开始,希望这可以帮助别人尝试做同样的事情。

如果您看到可以进行的任何改进,请告诉我。


最新通报在评论中提到

  1. NaN的问题已经解决,上面的代码更新。
  2. 波形图像更新以表示通过除去NaN值的固定的输出如在点指出1.

UPDATE 1个

平均电平(不RMS)进行了计算求和来确定最大每个样本点的水平除以总样本数。这方面的例子可以看到下面:

无声的WAV文件: enter image description here

拍手wav文件: enter image description here

布朗,粉红&白噪声的WAV文件: enter image description here

+1

很高兴你有这么多!如果您首先创建一个列表:'var points = data.ToList()。Select((y,x)=> new {x ,y})。Select(p => new PointF(px,py))。ToList();'。你也可以使用'Graphics.ScaleTranform'来玩所有的缩放,而不是缩放所有的坐标。像'g.ScaleTransform(0.1f,0.1f);'(或更少)就是一个开始,但是你应该使用'var xScale =(data.Max() - data.Min())/ imageSize.Width ;'等等。另外:你需要'Dispose()'的位图! – TaW

+0

@TaW您提到的第一部分我将不得不考虑缩放比例,更详细的以确保我理解该过程。我现在用Dispose()更新我的代码。尽管如此,我仍然需要处理NaN值。不知道为什么,但一旦我明白了这一点,也会更新代码。 – Majickal

+0

不知道我是否理解平均水平线。如果移除NaN(i.r. overflow)数据点也不是正确的方式来处理这些值。难道他们错过了总结,但计入分频器,从而改变了平均值?为什么东西溢出? – TaW

1

这里您可能想要的变体研究。它比例尺Graphics对象,所以它可以直接使用float数据。

请注意我如何翻译(即移动)绘图区域两次,以便我可以更方便地绘图!

它也使用DrawLines方法进行绘制。除了速度之外,其优势在于线条可以是半透明的或比一个像素更厚,而不会在关节处产生伪影。你可以看到中线闪耀。

要做到这一点,我使用一点点Linq magick将浮点数据转换为List<PointF>

我也确保把我创建的所有GDI +对象放在using子句中,这样它们才能正确处置。

enter image description here

... 
using System.Windows.Forms; 
using System.IO; 
using System.Drawing; 
using System.Drawing.Imaging; 
using System.Drawing.Drawing2D; 
.. 
.. 
class Program 
{ 
    static void Main(string[] args) 
    { 
     float[] data = initData(10000); 
     Size imgSize = new Size(1000, 400); 
     Bitmap bmp = drawGraph(data, imgSize , Color.Green, Color.Black); 
     bmp.Save("D:\\wave.png", ImageFormat.Png); 
    } 

    static float[] initData(int count) 
    { 
     float[] data = new float[count]; 

     for (int i = 0; i < count; i++) 
     { 
      data[i] = (float) ((Math.Sin(i/12f) * 880 + Math.Sin(i/15f) * 440 
           + Math.Sin(i/66) * 110)/Math.Pow((i+1), 0.33f)); 
     } 
     return data; 
    } 

    static Bitmap drawGraph(float[] data, Size size, Color ForeColor, Color BackColor) 
    { 
     Bitmap bmp = new System.Drawing.Bitmap(size.Width, size.Height, 
           PixelFormat.Format32bppArgb); 
     Padding borders = new Padding(20, 20, 10, 50); 
     Rectangle plotArea = new Rectangle(borders.Left, borders.Top, 
         size.Width - borders.Left - borders.Right, 
         size.Height - borders.Top - borders.Bottom); 
     using (Graphics g = Graphics.FromImage(bmp)) 
     using (Pen pen = new Pen(Color.FromArgb(224, ForeColor),1.75f)) 
     { 
      g.SmoothingMode = SmoothingMode.AntiAlias; 
      g.Clear(Color.Silver); 
      using (SolidBrush brush = new SolidBrush(BackColor)) 
       g.FillRectangle(brush, plotArea); 
      g.DrawRectangle(Pens.LightGoldenrodYellow, plotArea); 

      g.TranslateTransform(plotArea.Left, plotArea.Top); 

      g.DrawLine(Pens.White, 0, plotArea.Height/2, 
        plotArea.Width, plotArea.Height/2); 


      float dataHeight = Math.Max(data.Max(), - data.Min()) * 2; 
      float yScale = 1f * plotArea.Height/dataHeight; 
      float xScale = 1f * plotArea.Width/data.Length; 


      g.ScaleTransform(xScale, yScale); 
      g.TranslateTransform(0, dataHeight/2); 

      var points = data.ToList().Select((y, x) => new { x, y }) 
          .Select(p => new PointF(p.x, p.y)).ToList(); 

      g.DrawLines(pen, points.ToArray()); 

      g.ResetTransform(); 
      g.DrawString(data.Length.ToString("###,###,###,##0") + " points plotted.", 
       new Font("Consolas", 14f), Brushes.Black, 
       plotArea.Left, plotArea.Bottom + 2f); 
     } 
     return bmp; 
    } 
} 
+0

我喜欢你在这里所做的@Taw!我一定会更详细地看看这个,谢谢!直接绘制花车是一个好主意,而且只有在使用图表方法时才能做到,而图表方法非常慢,所以这将很好地解决问题。 – Majickal