如何渲染音频波形?

5
  1. 是否有任何與音訊/程式設計相關的堆疊交換網站?
  2. 我正在嘗試在WinForms中製作波形
  3. 我應該使用什麼演算法?

例如,如果每個像素(垂直線)有200個樣本,我應該繪製那部分200個樣本中最低和最高的樣本嗎?還是應該繪製低位和高位樣本的平均值?也許用不同的顏色繪製兩者都可以?


这背后并没有太多的算法。你有数字,来自音频文件的样本。用一条线连接这些点。折线是最好的选择。颜色和缩放完全取决于你的喜好。 - Hans Passant
是的,但仅当您使用非常大的缩放时才有效。如果每个像素有很多样本,则需要选择不同的解决方案。 - apocalypse
为什么不对样本进行采样 - 每个像素选择一个?如果您缩小到足够远,以至于200个样本由单个像素表示,我不确定知道范围内的最小和最大值有多有用。这就是放大的作用。 - anton.burger
如果我使用最小/最大样本,我可以轻松地看到模拟录音中可能存在的最高/最低值等错误。如果我将样本用作样本,我将看不到任何有趣的东西。如果我使用平均值,所有轨道看起来都很相似。 - apocalypse
2
这个问题之前已经被问过并在这里得到了回答。这是我给出的答案。http://stackoverflow.com/questions/11091924/drawing-waveform-converting-to-db-squashes-it/11104418 请尝试更多搜索。 - Bjorn Roche
5个回答

9
这将帮助您使用C#中的nAudio从音频文件生成波形...
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    string strPath = Server.MapPath("audio/060.mp3");
    string SongID = "2";
    byte[] bytes = File.ReadAllBytes(strPath);
    WriteToFile(SongID,strPath, bytes);
    Response.Redirect("Main.aspx");
    }

private void WriteToFile(string SongID, string strPath, byte[] Buffer)
{
    try
    {
        int samplesPerPixel = 128;
        long startPosition = 0;
        //FileStream newFile = new FileStream(GeneralUtils.Get_SongFilePath() + "/" + strPath, FileMode.Create);
        float[] data = FloatArrayFromByteArray(Buffer);

        Bitmap bmp = new Bitmap(1170, 200);

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

        NAudio.Wave.Mp3FileReader reader = new NAudio.Wave.Mp3FileReader(strPath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf));
        NAudio.Wave.WaveChannel32 channelStream = new NAudio.Wave.WaveChannel32(reader);

        int bytesPerSample = (reader.WaveFormat.BitsPerSample / 8) * channelStream.WaveFormat.Channels;

        using (Graphics g = Graphics.FromImage(bmp))
        {

            g.Clear(Color.White);
            Pen pen1 = new Pen(Color.Gray);
            int size = data.Length;

            string hexValue1 = "#009adf";
            Color colour1 = System.Drawing.ColorTranslator.FromHtml(hexValue1);
            pen1.Color = colour1;

            Stream wavestream = new NAudio.Wave.Mp3FileReader(strPath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf));

            wavestream.Position = 0;
            int bytesRead1;
            byte[] waveData1 = new byte[samplesPerPixel * bytesPerSample];
            wavestream.Position = startPosition + (width * bytesPerSample * samplesPerPixel);

            for (float x = 0; x < width; x++)
            {
                short low = 0;
                short high = 0;
                bytesRead1 = wavestream.Read(waveData1, 0, samplesPerPixel * bytesPerSample);
                if (bytesRead1 == 0)
                    break;
                for (int n = 0; n < bytesRead1; n += 2)
                {
                    short sample = BitConverter.ToInt16(waveData1, n);
                    if (sample < low) low = sample;
                    if (sample > high) high = sample;
                }
                float lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue);
                float highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue);
                float lowValue = height * lowPercent;
                float highValue = height * highPercent;
                g.DrawLine(pen1, x, lowValue, x, highValue);

            }
        }

        string filename = Server.MapPath("image/060.png");
        bmp.Save(filename);
        bmp.Dispose();

    }
catch (Exception e)
    {

    }
}
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;
}

}

3
谢谢,但这个问题有点旧了 :) 我已经完成了我的应用程序。http://i.imgur.com/DfAkGrv.png - apocalypse
嘿@zgnilec,我需要你的帮助。我也在尝试和你开发一样的东西。你能帮我完成吗?我只完成了波形生成。我还需要做缩放功能和在波形上移动线条。帮帮我。 - Illaya
然后通过电子邮件与我联系。我稍后会回答,因为我现在正在工作。 - apocalypse
multiverse at inbox dot com。发送您的所有问题。 - apocalypse
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/41870/discussion-between-illaya-and-zgnilec - Illaya
显示剩余3条评论

6
  1. 尝试访问dsp.stackexchange.com

  2. 每像素200个样本时,有几种方法可尝试。无论您选择什么方法,最好将每条垂直线都绘制在0的上方和下方,即分别处理正样本值和负样本值。可能最简单的是只计算RMS。在这样低的分辨率下,峰值通常会给您提供一个误导波形的表示。


1

那个项目还没有完成,作者说有时会导致错误。我需要构建更高级的东西。我试图将我的输出与Audacity波形进行比较,看起来非常相似。然而,我需要从Stack Overflow得到一些建议。 - apocalypse

1

如果有人遇到这个问题:

您可以将每像素样本视为缩放级别,在更高级别(缩小更多)上,您可能希望出于性能原因对其进行子采样。

您很可能希望有一个适合屏幕的固定宽度来绘制,并使用虚拟滚动(这样您就不会有数百万像素的绘图区域)。

您可以通过迭代音频数据并使用以下公式计算每个像素的值:跳过(滚动位置*每像素样本)+(像素*每像素样本)获取每像素样本。 这允许以高效的无限缩放和滚动方式进行操作,因为您只读取和绘制填充视图所需的最小量。 滚动宽度是通过音频数据长度/每像素样本计算的。

音频样本通常以两种方式显示,即样本范围的峰值或rms值。 rms值通过将样本范围内所有值的平方求和,将总和除以样本长度,rms值是其平方根(rms略高于平均值,是感知响度的良好指标)。

您可以通过多种方式提高性能,例如增加子采样(导致失去细节),限制滚动和使绘图请求可取消,以防在渲染之前出现新的滚动。


那么当你缩放时,你是跳过音频样本,对吗?例如,如果你处于2倍缩放状态,你会读取每个偶数音频样本并跳过奇数样本。这种方法存在许多问题,例如使用100倍因子进行下采样效果不佳。一种方法是在多个缩放级别上构建样本,但这又会消耗大量内存和启动时间。 - Deepak Sharma

0

只是为了记录,如果你想让音频文件填满输出图像的宽度

samplesPerPixel = (reader.Length / bytesPerSample) / width ;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接