【问题标题】:Open source C# code to present wave form?开源 C# 代码呈现波形?
【发布时间】:2010-11-15 23:09:30
【问题描述】:

是否有任何开源 C# 代码或库来呈现给定字节数组的图形波形?

【问题讨论】:

    标签: c# audio


    【解决方案1】:

    使用来自 robby 的改编代码并使用带有抗锯齿功能的 Graphics.Fill/DrawClosedCurve,我得到了一个非常漂亮的结果。

    代码如下:

    using System;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    
    namespace Soundfingerprinting.Audio.Services
    {
        public static class AudioVisualizationService
        {
            public class WaveVisualizationConfiguration
            {
                public Nullable<Color> AreaColor { get; set; }
                public Nullable<Color> EdgeColor { get; set; }
                public int EdgeSize { get; set; }
                public Nullable<Rectangle> Bounds { get; set; }
                public double Overlap { get; set; }
                public int Step { get; set; }
            }
    
            public static void DrawWave(float[] data, Bitmap bitmap, WaveVisualizationConfiguration config = null)
            {
                Color areaColor = Color.FromArgb(0x7F87CEFA);// Color.LightSkyBlue; semi transparent
                Color edgeColor = Color.DarkSlateBlue;
                int edgeSize = 2;
                int step = 2;
                double overlap = 0.10f; // would better use a windowing function
                Rectangle bounds = Rectangle.FromLTRB(0, 0, bitmap.Width, bitmap.Height);
    
                if (config != null)
                {
                    edgeSize = config.EdgeSize;
                    if (config.AreaColor.HasValue)
                        areaColor = config.AreaColor.GetValueOrDefault();
                    if (config.EdgeColor.HasValue)
                        edgeColor = config.EdgeColor.GetValueOrDefault();
                    if (config.Bounds.HasValue)
                        bounds = config.Bounds.GetValueOrDefault();
    
                    step = Math.Max(1, config.Step);
                    overlap = config.Overlap;
                }
    
                float width = bounds.Width;
                float height = bounds.Height;
    
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                    Pen edgePen = new Pen(edgeColor);
                    edgePen.LineJoin = LineJoin.Round;
                    edgePen.Width = edgeSize;
                    Brush areaBrush = new SolidBrush(areaColor);
    
                    float size = data.Length;
                    PointF[] topCurve = new PointF[(int)width / step];
                    PointF[] bottomCurve = new PointF[(int)width / step];
                    int idx = 0;
                    for (float iPixel = 0; iPixel < width; iPixel += step)
                    {
                        // determine start and end points within WAV
                        int start = (int)(iPixel * (size / width));
                        int end = (int)((iPixel + step) * (size / width));
                        int window = end - start;
                        start -= (int)(overlap * window);
                        end += (int)(overlap * window);
                        if (start < 0)
                            start = 0;
                        if (end > data.Length)
                            end = data.Length;
    
                        float posAvg, negAvg;
                        averages(data, start, end, out posAvg, out negAvg);
    
                        float yMax = height - ((posAvg + 1) * .5f * height);
                        float yMin = height - ((negAvg + 1) * .5f * height);
                        float xPos = iPixel + bounds.Left;
                        if (idx >= topCurve.Length)
                            idx = topCurve.Length - 1;
                        topCurve[idx] = new PointF(xPos, yMax);
                        bottomCurve[bottomCurve.Length - idx - 1] = new PointF(xPos, yMin);
                        idx++;
                    }
    
                    PointF[] curve = new PointF[topCurve.Length * 2];
                    Array.Copy(topCurve, curve, topCurve.Length);
                    Array.Copy(bottomCurve, 0, curve, topCurve.Length, bottomCurve.Length);
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    g.FillClosedCurve(areaBrush, curve, FillMode.Winding, 0.15f);
                    if (edgeSize > 0)
                        g.DrawClosedCurve(edgePen, curve, 0.15f, FillMode.Winding);
                }
    
            }
    
            private static void averages(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;
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我多年来一直是ZedGraph的粉丝,并用它来显示各种项目中的各种数据。

      以下示例代码绘制了一个在 -1 和 1 之间变化的双精度数组:

      void DisplayWaveGraph(ZedGraphControl graphControl, double[] waveData)
      {
          var pane = graphControl.GraphPane;
          pane.Chart.Border.IsVisible = false;
          pane.Chart.Fill.IsVisible = false;
          pane.Fill.Color = Color.Black;
          pane.Margin.All = 0;
          pane.Title.IsVisible = false;
          pane.XAxis.IsVisible = false;
          pane.XAxis.Scale.Max = waveData.Length - 1;
          pane.XAxis.Scale.Min = 0;
          pane.YAxis.IsVisible = false;
          pane.YAxis.Scale.Max = 1;
          pane.YAxis.Scale.Min = -1;
          var timeData = Enumerable.Range(0, waveData.Length)
                                   .Select(i => (double) i)
                                   .ToArray();
          pane.AddCurve(null, timeData, waveData, Color.Lime, SymbolType.None);
          graphControl.AxisChange();
      }
      

      上面的示例通过抑制轴和更改颜色来模拟音频编辑器的样式以生成以下内容:

      【讨论】:

        【解决方案3】:

        我稍微修改了MusiGenesis 的解决方案。 这给了我一个更好的结果,尤其是家庭音乐:)

        public static Bitmap DrawNormalizedAudio(List<float> data, Color foreColor, Color backColor, Size imageSize)
        {
            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);
                }
            }
        
            return bmp;
        }
        
        
        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];
                }
            }
        
            posAvg /= posCount;
            negAvg /= negCount;
        }
        

        【讨论】:

        • +1 我完全同意! thx 的补充。此方法以 SoundCloud.com 的样式提供波形。
        • @robyy 如何调用 Bitmap DrawNormalizedAudio(List data, Color foreColor, Color backColor, Size imageSize) 函数?
        • 有人说如何调用Bitmap DrawNormalizedAudio(List data, Color foreColor, Color backColor, Size imageSize) 函数?
        【解决方案4】:

        这是开源的:

        public static void DrawNormalizedAudio(ref float[] data, PictureBox pb,
            Color color)
        {
            Bitmap bmp;
            if (pb.Image == null)
            {
                bmp = new Bitmap(pb.Width, pb.Height);
            }
            else
            {
                bmp = (Bitmap)pb.Image;
            }
        
            int BORDER_WIDTH = 5;
            int width = bmp.Width - (2 * BORDER_WIDTH);
            int height = bmp.Height - (2 * BORDER_WIDTH);
        
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.Clear(Color.Black);
                Pen pen = new Pen(color);
                int size = data.Length;
                for (int iPixel = 0; iPixel < width; iPixel++)
                {
                    // determine start and end points within WAV
                    int start = (int)((float)iPixel * ((float)size / (float)width));
                    int end = (int)((float)(iPixel + 1) * ((float)size / (float)width));
                    float min = float.MaxValue;
                    float max = float.MinValue;
                    for (int i = start; i < end; i++)
                    {
                        float val = data[i];
                        min = val < min ? val : min;
                        max = val > max ? val : max;
                    }
                    int yMax = BORDER_WIDTH + height - (int)((max + 1) * .5 * height);
                    int yMin = BORDER_WIDTH + height - (int)((min + 1) * .5 * height);
                    g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax, 
                        iPixel + BORDER_WIDTH, yMin);
                }
            }
            pb.Image = bmp;
        }
        

        这个函数会产生这样的结果:

        这采用浮点格式的样本数组(其中所有样本值的范围从 -1 到 +1)。如果您的原始数据实际上是 byte[] 数组的形式,则您必须做一些工作才能将其转换为 float[]。如果您也需要,请告诉我。

        更新:由于问题在技术上要求渲染字节数组,这里有几个辅助方法:

        public float[] FloatArrayFromStream(System.IO.MemoryStream stream)
        {
            return FloatArrayFromByteArray(stream.GetBuffer());
        }
        
        public float[] FloatArrayFromByteArray(byte[] input)
        {
            float[] output = new float[input.Length / 4];
            for (int i = 0; i < output.Length; i++)
            {
                output[i] = BitConverter.ToSingle(input, i * 4);
            }
            return output;
        }
        

        更新 2:我忘了有更好的方法来做到这一点:

        public float[] FloatArrayFromByteArray(byte[] input)
        {
            float[] output = new float[input.Length / 4];
            Buffer.BlockCopy(input, 0, output, 0, input.Length);
            return output;
        }
        

        我想我只是爱上了for 循环。

        【讨论】:

        • 在您获得许可之前,每次使用收取 OP 20 美元 :)
        • @Martin:我只是为了而收费。我的 20 美元呢? :)
        • 酷算法。我只是在计算最接近我的像素的样本并在那里放置一个点,但是做那个像素范围的最大值和最小值看起来要好得多!
        • +1 为您服务!感谢您的代码!它有很大帮助。但是,我注意到辅助方法不是静态的,但绘图方法是静态的。只是想我会指出这一点(我猜叫它强迫症。)
        • @manny:您需要首先对数据进行规范化,以使值介于 -1 和 +1 之间。一种方法是遍历所有样本值并确定最大的绝对样本值,然后再次遍历样本并将每个样本除以该最大值;这将使所有样本都在 -1 和 +1 之间。或者,如果您的原始音频是红皮书格式(例如在 CD 上),您可以只除以可能的最大绝对样本值 (32768);这可能不会像第一种方法那样有效。
        【解决方案5】:

        NAudio 中,有用于在 WinForms 和 WPF 中绘制音频波形的代码。查看演示项目,了解如何使用它的示例。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-08-08
          • 2013-12-24
          • 2012-11-17
          • 1970-01-01
          • 2012-03-13
          相关资源
          最近更新 更多