Chromium Web Audio设计思想

ChromiumWeb Audio设计思想

作者:刘旭晖Raymond转载请注明出处

Email:colorant<at> 163.com

BLOG:http://blog.csdn.net/colorant/

 

本文是我在参与W3C Web Audio的相关的工作时对其设计思想的一点理解,对于WebAudio是什么,以及如何使用,本文不打算做过多的介绍,因此这里假定你使用过W3CWeb Audio,或者对其框架有一定的了解 ;)

需求分析

W3CWeb Audio(http://www.w3.org/TR/webaudio/)的设计出发点,是满足基于HTML5的复杂交互类程序,游戏或音频应用程序对音频处理能力的需求。通常这些应用会有如下的需求:

 

  • 大量动态并发音源
  • 精确的时间控制
  • 动态音频处理 (支持混音,动态音频生成,以及各种音频特效处理,波形变换等)
  • 低延迟
  • 多声道支持
  • 使用灵活,易于扩展

 针对这些需求,Web Audio的整体框架以及其具体实现(以Chromium中的实现为例)都做了相应的考量。

灵活性和扩展性

构建在结点(AudioNode)上的流水线框架

下图引用自Web Audio Spec


可以看到,整个音频处理的过程是由各类音频处理节点构建组成的。这里需要说明的是:我所指的流水线框架,更多的指的是数据从Source节点向Destination节点的串行流动处理过程。和传统的流水线概念不同的是,任何时候,只有一帧的音频数据在流水线中串行传递处理,并没有多帧的数据同时在流水线的不同节点上被处理。

 

从API开发者的角度:这种架构将各种音频处理算法模块化,易于拓展API能力。

从应用开发者的角度:可以按照自己的需求,创建和连接各类音频处理结点,构建满足应用需求的音频处理流程。 

用户定制音频处理算法的能力 

通过使用JavaScriptAudioNode(http://www.w3.org/TR/webaudio/#JavaScriptAudioNode)可以将音频数据流从流水线的任意位置暴露给用户,从而使用户可以定制化音频处理算法。 

动态音源的支持

要支持大量的动态并发音源(如游戏中的各种音效),有几个问题需要解决 

AudioNode的生命周期管理

首先为了支持音频的Fire andForget的使用模式,要保证一个音源片断创建完毕并开始播放以后,在JS空间中无需保留对应结点对象,音频流水线还能正常工作。其次当音源播放完毕后要能够及时销毁相关对象,减少内存占用。这就要求对AudioNode乃至整个AudioContext的生命周期都需要进行合理有效的管理。

 

Chromium中Web Audio的实现对音频结点按照多种方式进行了引用计数:除了正常的对象引用计数,还按照音频结点上下游连接关系建立了"Connection"连接引用计数,此外对音源结点(AudioSourceNode)使用了特殊连接引用计数方式(由AudioContext对AudioSourceNode默认增加连接引用计数,在音源播放完毕后移除对应的连接引用计数)从而保证Fire andForget模式的正常工作。 

动态声道切换支持  

在一个复杂的应用程序中,音频数据的来源和格式可能不同,举例来说,一个游戏中的背景音乐可能是立体声,而环境音效等动态音源则可能是单声道的,在别的程序中则可能反过来,又或者需要动态添加5.1环绕立体声的效果。

 

在Web Audio的实现中,有大量的代码是围绕着支持动态调整音频数据流的声道数来设计的,比如输入输出结点的声道数自适应匹配,不同声道数音频数据流的Up/DownMixing混音等。

 

当然,由于很多的音频处理算法和音频数据流的声道数是相关联的,某些类型的AudioNode在目前的WebAudio实现中还不能做到对所有声道数的自适应匹配。

 

此外某些音频处理结点(比如DynamicsCompressorNode)的算法依赖于历史数据,频繁的切换声道数可能造成算法效果变差,所以这些结点对声道数的自适应还有一些特殊的处理。 

音频处理的实时性和低延迟 

影响音频处理实时性的因数很多,要保证低延迟,不间断的输出音频数据,就需要从各个方面加以综合考虑,比如:在音频输出端,缓存足够长度的待输出音频数据,减小CPU负荷波动造成前端数据无法及时处理带来的影响;使用共享内存,减少数据拷贝等。在Chromium的实现中,Web Audio的角度来看,Browser进程对音频数据的处理不在其控制范围之类,在其自身的设计中所做的努力包括 

对音频数据的合理分割 

首先在WebAudio的处理流程中,每个节拍(每帧)所处理的数据长度,当然是需要固定为一个常量,避免各个节点频繁分配其处理流程中使用的相关内存和数据结构。其次,许多算法的效率和数据的长度相关,尤其是频域变换相关算法。最后,每帧数据的长度也会对处理的实时性和所占用内存的大小带来直接影响。综合考虑的结果,目前每帧数据的长度设置为128(这里指的是各声道的采样个数,而非绝对字节长度)

 

此外,部分结点(RealtimeAnalyserNode/JavaScriptAudioNode等)可能出于自身特殊需求或算法的考虑,会使用不同的长度来处理数据,通常的做法是积累若干帧的数据再做一次处理。

独立的Audio Thread 

Render进程需要处理的事情太多,很难确保当音频数据需要处理时Render不会正忙于渲染网页或处理JS代码等, 要保证音频数据能够得到实时的处理,就需要使用单独的AudioThread来运行Web Audio的流水线。不单Web Audio的render进程这端如此,实际上browser进程中对Audio数据的处理也使用了单独的Thread。

 

使用Thread的同时自然也就带来了同步的问题,Web Audio的实现中也有大量的代码是针对同步问题设计的,某些具体节点的实现方式更是受到了线程同步问题的深刻影响。 

AudioGraph 拓扑结构的同步

首先是流水线结构的同步。因为Web Audio允许用户动态的修改流水线结构,添加新的节点,修改连接关系等,而这些都是用户的JS代码在Render主线程中执行的结果,而流水线结构的使用则是在Audio thread中。此外,当一个音源节点在Audiothread中播放完毕以后,也会由Audio thread发起流水线结构的修改动作,而流水线结构的修改最终也可能导致音频节点生命周期的变更。这就要求对流水线结构的修改动作需要采取一定的加锁机制和同步策略。

 

由于线程同步问题可能发生在代码的任何地方,Web Audio中解决同步问题的一个根本思想,是将问题集中到有限的时间和区域中,降低问题的牵涉面和复杂度。

 

首先Audio线程被划分为3个时间段,正在Render一帧数据中,和pre/post render阶段。而音频流水线结构关系也是分前台和后台两个数据结构管理的。所有在render时间段中发生的流水线结构变更,都只修改后台数据结构,在pre/post阶段才真正同步到前台数据结构中,以避免对当前流水线流程的影响。此外对节点的生命周期的管理也是类似,在render阶段发生的变更都只是记录下来,推迟到pre/post阶段去处理。在这些过程中如果涉及到锁的操作,为了保证音频数据的不间断处理,原则上都是主线程可以阻塞在锁的获取上,而Audio线程则永远不阻塞,如果获取锁失败,就放弃相关操作,推后到下一个时间片断再尝试,通过这种方式大大降低了线程同步的复杂度。 

异步和同步操作的调和

为了保证音频输出的连续性,Audio线程流水线的处理不应该被中断,所有数据都必须能够同步的得到实时的处理。所以本质上,是不允许流水线过程中存在异步操作和阻塞等待的。但是,由于有类似JavaScriptAudioNode这样的将数据暴露给JS空间处理的节点的存在,需要在线程间等待和同步数据的处理流程,也就带来了类似异步操作的过程。对于这一类型的问题,Web Audio的总体解决思路就是拷贝复制当前帧的数据到额外的存储空间中,等待数据处理完毕后再输出。至于如何处理当前帧因该输出的数据,不同的节点有不同的处理方式,但是都必须保证有等量的数据得到输出,不打断数据的流动。

 

当然,由于本质上这两者就是不可调和的,所以这样的处理方式在适用场合方面有着它的局限性。 

In Place 的数据处理 

流水线式的串行操作通常会涉及到数据的拷贝问题,要减少数据的拷贝,最好是各个节点能够使用同一块内存空间来处理音频数据,但是由于流水线拓扑结构的动态改变,音源数据的来源和格式的不同,给这个任务带来很大的难度。总体而言,Web Audio的处理方式是尽量保证音频数据的Layout(主要是声道数)的统一(比如自动匹配输入输出节点的声道数,及时up/downmixing等),在数据Layout一致的情况下,如果节点的算法允许,尽量直接在下游节点传递过来的内存上处理数据,减少内存拷贝带来的开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值