从音频绘制波形的算法

20

我正在尝试从原始音频文件中绘制波形。 我使用FFmpeg解复用/解码音频文件,我拥有以下信息:样本缓冲区,样本缓冲区的大小,音频文件的持续时间(以秒为单位),采样率(44100、48000等),采样大小,样本格式(uint8、int16、int32、float、double)和原始音频数据。

在互联网上探索时,我找到了这个算法(更多内容在这里):

白噪声:

White Noise

该算法如下:

  

你只需要将每个样本从-amplitude到amplitude随机化。 在大多数情况下,我们不关心大多数通道的数量,因此我们只需用新的随机数填充每个样本。

Random rnd = new Random();
short randomValue = 0;

for (int i = 0; i < numSamples; i++)
{
    randomValue = Convert.ToInt16(rnd.Next(-amplitude, amplitude));
    data.shortArray[i] = randomValue;
}

这很好,但我不想那样画,我想这样画:

audacity

是否有任何算法或思路,可以利用我所拥有的信息来绘制图形?


您似乎正在尝试使用波形生成算法,而实际上您已经拥有了自己的波形。因此,在我看来,您根本不需要做任何事情。 - Galik
@Galik - 你说的“你已经有波形了”是什么意思?我唯一拥有的就是上面列出的原始文件信息,现在我正在寻找一种算法来使用这些信息绘制波形。 - yayuj
好的,你提供的算法与绘制波形无关,它们只是用来生成波形的。而你使用FFmpeg将原始音频数据转换成了波形,所以你不需要一个生成器。我认为你需要的可能是一种GUI框架,可以让你在屏幕上绘制东西。 - Galik
@Galik - 我明白了。我可以使用Qt与Canvas或OpenGL,但这正是关键,使用Canvas或OpenGL绘制这些信息。 - yayuj
我认为您需要选择一个框架,然后具体针对此框架提出问题,因为每个框架的工作方式都略有不同。 - Galik
首先,您需要获取音频文件中的示例,而不仅仅是元数据。听起来你还没有那个... - Steve
7个回答

29

为大家解释

我是一款DJ应用程序的开发人员,正在寻找类似的答案。所以我将解释关于音乐波形的所有内容,您可能会在包括Audacity在内的任何软件中看到。

任何音乐软件中都使用了3种类型的波形来显示。分别是样本、平均值和RMS。

1)样本是以图形方式呈现的实际音乐点,可以是原始音频数据的数组(在Audacity中缩放波形时看到的点)。

2)平均值:最常用的,假设您在屏幕上显示3分钟的歌曲,因此屏幕上的单个点必须至少显示100ms(约)的歌曲,其中有许多原始音频点,因此为了显示,我们计算该持续时间内所有点的平均值,然后为其余部分进行计算(Audacity中的深蓝色大波形)。

3)RMS:与平均值类似,但此处除了平均值外,还会采取特定持续时间的均方根(蓝色波形中小的浅蓝色波形是rms波形在Audacity中)。

现在让我们看看如何计算波形。

1)样本是解码歌曲时使用任何技术获得的原始数据。现在根据点的格式将它们转换为-1到1的范围,例如,如果格式为16位,则将所有点除以32768(16位数字的最大范围),然后绘制这些点。

2)对于平均波形-首先将所有点相加,并将负值转换为正值,然后乘以2,最后取平均值。

//samples is the array and nb_samples is the length of array
float sum = 0;
for(int i = 0 ; i < nb_samples ; i++){
    if(samples[i] < 0)
        sum += -samples[i];
    else
        sum += samples[i];
}
float average_point = (sum * 2) / nb_samples; //average after multiplying by 2
//now draw this point

3) RMS:它很简单,取根均方 - 首先平方每个样本,然后求和,然后计算平均值,最后开平方。我将在编程中展示

//samples is the array and nb_samples is the length of array
float squaredsum = 0;
for(int i = 0 ; i < nb_samples ; i++){
    squaredsum += samples[i] * samples[i]; // square and sum
}
float mean = squaredsum / nb_samples; // calculated mean
float rms_point = Math.sqrt(mean); //now calculate square root in last
//now draw this point

请注意,这里的“samples”是用于计算特定歌曲持续时间内点/像素的点数组。例如,如果您想在60个像素中绘制1分钟的歌曲数据,则样本数组将是每秒钟所有点的数组,即要在1个像素中显示的音频点数。

希望这能帮助有人澄清关于音频波形的概念。


2
经过自己的研究和实验,我发现计算每个点(原始文件的样本窗口)的最大值和最小值,并将最大值向上绘制,最小值向下绘制(我计划将算法作为答案发布),看起来最接近我尝试过的所有音频软件(Reaper、Audacity、Reason、Live)。当遵循“平均方法”时,波形会显著缩小,我必须再次缩放它,以获得漂亮的结果,这与最小/最大方法或我在常见的DAW中看到的东西明显不同。 - v01pe
1
请注意,squaredsum 在此处为浮点数非常重要 - 我曾尝试自己实现这个功能,但由于 int32 squaredsum 溢出而导致 rms 波形消失。 - CraftedCart
@CraftedCart 在我看来,从样本中提取最大值和绘制rms值是两个最重要的波形。平均方法适用于完整的歌曲波形(仅少数软件使用),但大多数情况下rms用于完整的歌曲波形(不包括sqrt部分)。 - Diljeet

9
首先,您需要确定每个样本在屏幕上的位置。
int x = x0 + sample_number * (xn - x0) / number_of_samples;

现在,对于所有具有相同 x 值的样本,分别确定正值和负值的最小值和最大值。从负最大值到正最大值绘制一条垂直的深色线,然后在其上方从负最小值到正最小值绘制一条浅色线。
编辑:再考虑一下,你可能想在内部线条中使用平均值而不是最小值。

6

“最大正数和最小负数” - 我进行了一些实验,这看起来是最接近的。 - v01pe
在源代码中找到了它:WaveClip::GetWaveDisplay:https://github.com/audacity/audacity/blob/master/src/WaveClip.cpp - 他们为显示计算每个窗口的最小值、最大值和RMS。 - v01pe
我认为现在它在https://github.com/audacity/audacity/blob/master/src/tracks/playabletrack/wavetrack/ui/WaveformCache.cpp中。 - undefined

3

showwavespic

ffmpeg可以使用showwavespic过滤器来绘制波形图。

enter image description here

ffmpeg -i input -filter_complex "showwavespic=split_channels=1" output.png

有关选项,请参见showwavespic过滤器文档

showwaves

您还可以使用showwaves过滤器制作实时波形的视频。

ffmpeg -i input -filter_complex \
"showwaves=s=600x240:mode=line:split_channels=1,format=yuv420p[v]"  \
-map "[v]" -map 0:a -movflags +faststart output.mp4

有关选项,请参阅showwaves过滤器文档


我在想是否有一种方法可以导出波形数据(而不是PCM数据,而是经过RMS处理的样本)。我手动从PCM数据中计算RMS,但速度相当慢,所以我认为ffmpeg可能有一个用于此的滤镜。 - Mehmet Efe Akça
@MehmetEfeAkça 应该作为一个新问题来询问。 - llogan

2

有一个很好的程序audiowaveform来自BBC R&D,可以实现你想要的功能,你可以参考他们的源代码。


肯定会有帮助的。谢谢。 - yayuj

1
底部的图表仅包含更长的时间跨度,因此如果您增加了numSamples,则会得到更紧密的图表。但是,对于白噪声,您不会看到在正常声音/音乐中会发现的峰和谷。因此,如果您可以增加样本大小,或者至少增加样本周期(x轴),则将开始模拟底部图表。使用其中两个以获得立体声效果。

1
第二个波形可能是一个简单的锯齿形图的列近似。

每个列都是前一个采样振幅到当前采样振幅的线条。

因此,将所有样本作为点预先测试读入画布或纹理,然后,一旦完成,您可以做两种情况,用条代替点,向上绘制到上一个采样或向上绘制到该采样,具体取决于哪个更高,只要在两者之间画一条线。这确保了波形在下一个采样之间能量较低且高能量时较小。

您可以对其进行别名处理并测量多个样本,这取决于您使用的硬件,是否要读取1000个样本并将其制成巨大的2D数组表示形式,然后向下别名处理为可显示的较小图像,或者只想仅运行512个样本并快速更新。在程序中使用2D画布,应该可以快速制作具有超过512个样本的详细波形

...另一种选择与其他答案中的灰色波形相同,将绝对值作为线从+当前样本绘制到-当前样本。

平均多个样本即每4个样本或获得每4个样本的最大值有助于具有不太奇怪的图形,这是一种快速的别名处理。


1
我根据使用条形图来近似曲折图的方法编写了一些代码,我发现Canvas绘图函数在处理内存时有点慢。我发现,如果将44100除以8或16,使用每16个样本中的最大值,仍然可以保持图像清晰,SR为2900次/秒,这很好,并且节省内存...我发现,显卡比尝试在纹理中显示顶点要快得多,所以我制作了一个由平面多边形组成的线框图形,在2D Canvas代码中速度更快。与发送到图形着色器以生成多边形相比,你可能会错过很多内容。DX11代码位于Unity论坛上。 - predatflaps

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