2016-09-20 57 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