用C / C ++快速显示波形

时间:2016-05-31 19:24:56

标签: c++ algorithm performance data-structures

我有兴趣在Windows和Linux上用C或C ++实现音频编辑器。我无法弄清楚如何在完全缩小的视图中足够快地显示波形。我不是在寻找有关快速帧缓冲技术的信息。这是一个关于算法和数据结构的问题,可以有效地确定要显示的内容。

说我希望能够编辑2小时长的5声道,48 KHz,24位声音。这是5千兆字节的样本数据。我希望能够从每个样本的一个像素一直缩小,直到所有样本数据一次可见。我希望应用程序能够感觉到响应,即使是在慢速机器上,例如,为了论证,1 GHz Atom。当我说响应时,我希望GUI更新通常发生在用户输入的1/30秒内。

在确定完全缩小视图的渲染内容时,一个简单的实现将扫描整个波形中的每个样本 - 它需要找到显示的每个像素宽度“覆盖”的所有样本的最大和最小样本值。我写了一个简单的应用来测试这种方法的速度。我在2015款3.5 GHz Xeon上测试了1小时长的单声道,16位,44.1 KHz采样。需要0.12秒。这太慢了几百倍。

您可以想象维护缩小数据的缓存,但我看不出如何避免在大多数插入或删除后重新计算整个缓存。感觉必须有更好的方式。

这是一个显示我想要实现的目标的图表:

enter image description here

这是大多数当前可用音频编辑器的显示方式。用户可能会期待这种行为。我用Audacity进行测试,它以这种方式工作(尽管它也显示了类似于浅色的样本的平均值)。它可以处理任意插入大声音,似乎是即时的。我不打算阅读75兆字节的源代码来了解它是如何做到的。

修改

各种各样的人都提出了在显示缩小视图时仅涉及考虑样本子集的方案。我得出的结论是,我不想这样做,因为它丢失了太多有用的信息。例如,如果您正在寻找声音中的毛刺,例如在乙烯基转换中点击,则包括所有样本非常重要。在最坏的情况下,如果毛刺只有一个样本长,我仍然希望保证它在完全缩小的视图中显示。

3 个答案:

答案 0 :(得分:2)

当缩放位于每个像素有多个样本的点时,不值得准确计算每个像素的平均样本值。用户无法在该缩放级别准确对齐GUI工具,因此没有任何好处。用户只需要一个定性视图。

我只想为窗口区域的每个屏幕像素选择一个样本,跳过不必要的样本。

像这样的完全未经测试的代码:

if (!me.store.proxy.hasOwnProperty('filterParam')) {
        me.store.proxy.filterParam = me.paramName;
}

显然你还需要考虑窗口滚动等...

答案 1 :(得分:2)

也许您可以使用图形中的mip-mapping技术,使用更多内存进行交易以获得更快的速度?

如果您有32个样本,请保持缩小的缓存x2,x4,x8,...存储此数据将再次占用与原始数据相同的空间(16 + 8 + 4 + 2 + 1个样本)。

视觉指南,.代表存储的数据点(最小/最大样本值)和_上一个.涵盖的样本:

1st level: ................
2nd level: ._._._._._._._._
3rd level: .___.___.___.___
4th level: ._______._______
5th level: ._______________

然后只需查询缩放级别的适当级别mip-map。

是的,在插入/删除样本时,您必须重新创建mip-map缓存(或​​部分缓存)。

但也许内存使用量不适合你?

修改

如果添加和删除是一个频繁的操作,并且不希望重新计算缓存(并且您希望在间隔内而不是仅在单个点上进行精确的下采样),那么您可以更改mip-mapping方法以存储对齐的数据到本地最小/最大采样点而不是基于时间的网格。

使用--------|--------表示一段时间内的本地最小/最大值,这里有一个图形表示:

                             --------|--------
   --------|--------
                   --------|--------
                                     --------|--
------|--------
                                     .
           .                        . .
.     .   . .   .          .       .   .     .
 . ... . .   . . .   . .. . .     .     .   . .
  .     .     .   . . .  .   .   .       . .   .
                   .          . .         .
                               .
--------|--------
           --------|--------
                                  --------|-----
                       --------|--------

然后添加和删除只需要重新计算添加/删除部分开头和结尾的直接局部区域。

您可能希望索引本地最小/最大值,因此您不需要进行大量搜索。一个更复杂的实施方案 - 对你来说可能不值得吗?

答案 2 :(得分:0)

在阅读Peter Stock的回答后,我想出了以下方案。我认为显示计算的速度比朴素方案快500倍,并且不应该为插入或删除添加任何明显的成本。内存开销不到1%。

声音数据将以131072个样本的块分配,因此插入和删除不需要重新分配和复制整个声音。首次加载声音时,每个块将完全填满(可能是最后一个)。插入和删除将导致一种碎片。为简单起见,我将安排每个块的开始始终包含有效的样本数据,并且任何间隙都将在块的末尾。

每个块都有两个与之关联的查找表,一个用于最大值,一个用于最小值。查找表中的每个项目对应1024个样本。

下图显示了如何计算显示器一个像素宽度的最大值。它显示了几个与计算相关的块。它假设没有“碎片化”。

Display calculation for one pixel width (no fragmentation)

插入后,情况稍微复杂一些。两个块现在在其末端具有无效区域。最大查找表中的条目现在对应于样本的部分空区域。只需获取 存在的样本的最大值即可找到这些条目的值。

Display calculation for one pixel width (with fragmentation)