3 音频
3.1 音频元素
说完视频之后,接下来就是HTML5中对音频的支持情况。音频支持不仅指对声音的播放,还包括对音频的编辑和合成,以及对乐器数字接口(MIDI)等的支持,下面逐次介绍并分析它们。
3.1.1 HTML5 Audio元素
说到音频,最简单当然也是最直接想到的就是音频播放,在HTML5中使用“audio”元素来表示。同视频类似,HTML5标准中也定义了三种格式,它们是Ogg、MP3和Wav。到目前为止,笔者所了解的浏览器对音频格式的支持如表11-2所示。
表11-2 主流浏览器对HTML5中三个音频格式的支持
与视频格式类似,考虑到浏览器对HTML5的“audio”支持程度不同,格式也不尽相同,所以Web开发者同样可以提供三种格式的文件,采用如实例代码11-4所示的代码以获得最好的用户体验效果。
示例代码11-4 使用“audio”元素的HTML5代码片段
<audio controls="controls"> <source src="audio.mp3" type="audio/mpeg"> <source src=" audio.wav" type="audio/wav"> <source src=" audio.ogg" type="audio/ogg"> Your browser does not support the audio tag. </audio>
因为视频内容通常包含音频数据,所以不仅仅是“audio”元素才会使用音频播放,但是二者在工作原理上是类似的。同时,音频的字幕同视频是一样的,“track”元素也可以用在“audio”元素的字幕中,用来显示字幕。
3.1.2 基础设施
音频的支持方面还是从输入和输出两个方面着手。对于输入,同视频类似,WebKit使用资源加载器先加载音频文件,之后是建立音频元素、管线化引擎相关的类,如MediaPlayer类、HTMLAudioElement和WebMediaPlayer类等。同视频不一样的是,视频的输出是GPU中的纹理对象,而音频需要输出到声卡,所以需要打开声卡设备。因为Chromium的沙箱模型机制,所以只能靠Browser进程来打开和关闭声卡设备,图11-14描述了多进程中如何将音频从Renderer进程传输到Browser进程,以及WebKit和Chromium中相应的基础设施。
图11-14 WebKit和Chromium支持Audio的基础设施
首先是Renderer进程这一部分,也就是右侧部分,从上往下依次介绍如下。
- WebKit::WebAudioSourceProvider 和WebKit::WebAudioSourceProviderClient :最上面两个类是WebKit的Chromium移植接口类,根据名字读者已经猜测出来了,前者提供音频原数据,也就是音频文件中的数据,这里采用“拉”的方式,也就是在ResourceLoader加载数据之后,当且仅当渲染引擎需要新的数据的时候,主动从加载后的数据中拉出数据来进行解码。“provideInput”函数由Chromium实现,由WebKit引擎调用。WebKit::WebAudioSourceProviderClient提供了“setFormat”函数,用于让Chromium的媒体播放器设置频道数量、采样率等信息。WebAudioSourceProviderImpl是WebKit::Web-AudioSourceProvider的实现类。
- AudioRendererImpI :该类就是音频渲染器的实现,并使用类AudioRenderSink将音频解码的结果输出到音频设备。
- AudioRendererSink :一个抽象类,用于表示一个音频终端点,能够接收解码后的音频信息,典型的一个例子就是音频设备。
- AudioRendererMixer :渲染器中的调音类。
- AudioOutputDevice :音频的输出设备,当然只是一个桥接层,因为实际的调用请求是通过下面两个类传送给Browser进程的。
- AudioOutputIPCImpI和AudioMessageFiIter :前者将数据和指令通过IPC发送给Browser进程,而后者当然就是执行消息发送机制的类。
然后是Browser进程这一部分,也就是左侧部分,自下而上依次介绍如下。
- AudioRendererHost :Browser进程端同Renderer进程通信并有调度管理输出视频流的功能,对于每个输出流,有相应的AudioOutputStream对象对应,并且通过AudioOutputController类来处理和优化输出。
- AudioOutputControIIer :该类控制一个AudioOutputStream对象并提供数据给该对象,提供play、pause、stop等功能,因为它控制着音频的输出结果。
- AudioOutputStream 和AudioOutputProxy :音频的输出流类和其子类。AudioOutputProxy是一个使用优化算法的类,它仅在Start()和Stop()函数之间打开音频设备,其他情况下音频设备都是关闭的。AudioOutputProxy使用AudioOutputDispatcher打开和关闭实际的物理音频设备。
- AudioOutputDispatcher 和AudioOutputDispatcherImpI :控制音频设备的接口类和实际实现类。
经过上面对类的解释,相信读者有了一个大致的思路:当WebKit和Chromium需要输出解码后的音频数据时,通过从右侧自上向下、左侧自下向上的过程,然后使用共享内存的方式将解码后的数据输出到实际的物理设备中。
3.2 Web Audio
Audio元素能够用来播放各种格式的音频,但是,HTML5还拥有更强大的能力来处理声音,这就是Web Audio。该规范提供了高层次的JavaScript接口,用来处理和合成声音。整个思路就是提供一张图,该图中的每个节点称为AudioNode,这些节点构成处理的整个过程,虽然实际的处理是使用C/C++来完成的,但是Web Audio也提供了一些接口来让Web前端开发者使用JavaScript代码来调用C/C++的实现。WebAudio对于很多Web应用很有帮助,例如游戏,它能够帮助开发者设计和实时合成出各种音效。
根据W3C的Web Audio规范的定义,整个处理过程可以看成一个拓扑图,该图有一个或多个输入源,称为Source节点。中间的所有点都可以看成各种处理过程,它们组成复杂的网。图中有一个最终节点称为“Destination”,它可以表示实际的音频设备,每个图只能有一个该类型的节点。上述图中的所有节点都是工作在一个上下文中,称为AudioContext,如图11-15所示。
图11-15 使用WebAudio技术的音频图
对于Source1节点,它没有输入节点,只有输出节点。对于中间的这些节点,它们既包含输入节点也包含输出节点,而对于Destination节点,它只有输入节点,没有输出节点。上述图中的其他节点都是可以任意定义的,这些节点每一个都可以代表一种处理算法,开发者可以根据需要设置不同的节点以处理出不同效果的音频输出。
中间这些节点有很多类型,它们的作用也不一样,这些节点的实现通常由C或者C++代码来完成以达到高性能,当然这里提供的接口都是JavaScript接口。
Web Audio的绝大多数处理都是在WebKit中完成的,而不需要Chromium过多地参与,除了输入源和输出结果到实际设备,其他同前面的多媒体数据源是一致的,不再赘述,下面描述图11-15中的结构是如何被WebKit支持的。
图11-16的上半部分主要是支持规范中的标准接口,例如AudioBufferSourceNode、AudioContext、DestinationNode和OscillatorNode等类,它们对应图11-15中规范定义的接口,还有众多的类这里并没有绘制出。下面重点关注的是OsicllatorNode类,它需要对音频数据进行大量计算,包括向量的加法、乘法等。同时该节点类需要使用PeriodicWave来计算周期性波形,这里面需要使用到FFT(快速傅立叶变换)算法,因为音频的及时性,网页对性能有非常高的要求。对于Chromium移植,不同平台采用不同的加速算法,在Windows和Linux上使用FFmpeg中的高性能算法,在Android上使用OpenMax DL提供的接口来加速,而在Mac上又是不同的算法。
图11-16 WebKit支持WebAudio的一些重要基础设施
Web Audio对于Web领域来说很重要,一个重要的应用场景就是游戏,因为游戏中进场需要合成音效或者变换出不同的音效结果,笔者很乐意看到它在HTML5中发展得越来越好,推动游戏等应用领域的发展。
3.3 MIDI和Web MIDI
MIDI是一个通信标准,它是电子乐器之间,以及电子乐器与电脑之间的统一交流协议,用以确定电脑音乐程序、合成器和其他电子音响设备互相交换信息与控制信号的方法。同其他的声音格式不同,MIDI不是记录采样信息,而是记录乐器的演奏指令。现在,在Web中支持MIDI变成了一个非常热门的话题。
音频也可以以MIDI格式来存储,但是该格式并不是HTML5的标准,所以浏览器并没有内置地支持它们。为了能让MIDI格式的音乐播放出来,可以使用JavaScript代码,这就是MIDI.js。它使用上面提到的WebAudio技术和Audio元素来实现音乐的播放,在Chromium中效果非常不错。
但是这也只能做到播放MIDI格式的音频文件,能否在JavaScript中利用代码来控制MIDI输入和输出设备呢?也就是能够读入MIDI的输入信息,由JavaScript代码将其信息处理成MIDI格式的指令并传送到输出设备,这就是现在兴起的Web MIDI技术。目前的规范定义了一系列接口来接收和发送MIDI指令,但是该技术本身并不提供语义上的控制,而只是负责传输这些指令,所以渲染引擎其实并不知道这些指令的实际含义,这是跟Web Audio非常不一样的地方。
根据上面的描述,Web MIDI规范中定义了输入和输出的MIDI设备,如MIDIInput和MIDIOutput,通过MIDIAccess接口返回到所有枚举的输入和输出设备。MIDIInput主要包含一个接收指令的函数,叫onMessage,而MIDIOutput包含一个发送指令的函数,叫send,而发送的数据指令就是MIDIEvent,该指令包含一个时间戳和数据。MIDI规范也是草案阶段,所以支持它的浏览器很少,在2013年6月,Google渲染在Chromium的开发者版本中加入了Web MIDI的支持,这是一个非常大的进展,虽然只是处于起步阶段。
WebKit和Chromium对于Web MIDI的支持主要包括三个部分,第一是加入JavaScript的绑定,这个技术前面已经介绍过了;第二是将对MIDI接口的支持从Renderer进程桥接到Browser进程;第三是Chromium的具体实现,如图11-17所示。
图11-17 Chromium中的MIDI实现类们
3.4 Web Speech
HTML5对声音方面的支持绝不仅仅是上面介绍的这么多,现在还有一项非常重要的应用,那就是语音识别技术(Speech-to-Text)和合成语音技术(Text-to-Speech),它们已经被广泛地应用在很多领域。简单来说,就是从语音识别出文本文字和从文本文字生成声音资源。
HTML5中的“Web Speech API”是由Google公司发起的规范,其目的是将语音识别和合成语音技术提供给JavaScript接口,这样Web前端开发者可以在网页中使用它们。所以,这一规范主要包括两个接口:SpeechRecognition和SpeechSynthesis,分别标识上述两种功能。
自然而然地,W3C定义了两个主要的接口,分别对应识别和合成技术,接口定义比较清晰简单,例如对于SpeechRecognition,当调用start()函数时就开始语音识别,而后面的事件句柄则是让开发者知道识别的状态,当识别完成之后,可以通过监听“onresult”来获取识别的结果。
图11-18 W3C Speech API草案定义的主要接口
而对于SpeechSynthesis接口,规范定义的主要是speak()方法,该方法的参数SpeechSynthesisUtterance非常重要。该参数包含了输入的文本,并能提供各种合成过程中的状态,实际输出结果可以通过调用getVoices()函数来获得语音结果。
遗憾的是,目前Chromium中对该规范的实现依赖于Google API(也就是网络服务接口),这也意味着用户不能离线使用这些功能,因为语音的识别是需要服务器端提供的能力。同时还有一个问题,如果开发者希望自己在Chromium中编译一个浏览器或者其他应用,可是这些功能是受限的,开发者必须向Google申请一个称为“API Keys”的密钥文件,也就是必须获得使用这些Google API的授权。在未来,笔者希望能够有不依赖于服务的语音识别和合成语音技术的出现。