同步音量变化非常容易,因为Web音频API允许我们将诸如音量变化之类的时间安排到1千万分之一秒。 (真的!这是十亿分之一的百万分之一。当然,您的硬件可能无法达到这种精度,但这意味着在同一时间安排的两个事件肯定会同时发生。)
但是我们的问题才刚刚开始。
一段音频多长时间?
当您使用context.decodeAudioData(…)解码音频块时,所得的AudioBuffer具有duration属性,该属性表示剪辑的长度(以秒为单位)。 我们需要知道,以便安排后续块的回放。
问题是, 持续时间很可能是一部完整的小说。
Safari会根据mp3文件声称的持续时间(而不是实际解码的时间),根据duration属性读取第一个块的文件标题,而不是帧标题 。 在那之后,它做得更好,但是如果第二个块开始播放几分钟又太迟了,那对我们没有帮助。
同时,Chrome本质上会吐出随机数。 对于CBR文件(以恒定比特率(例如128kbps)编码的文件)来说,这很好,但是对于可变比特率(VBR)文件,它可能会被低估。
当浏览器API失败时,我们只有一种选择:用JavaScript的硬方法实现逻辑。
mp3文件的剖析
为了做到这一点,我们需要了解我们正在处理的数据在1和0级别。 有用的是,尽管mp3规范受版权保护(如果您愿意为之付费,则可以获取该文件;我不会),但是善良而聪明的人们对它进行了反向工程,并免费提供了他们的发现。 (如果您对此东西感兴趣,让我们来构建比约恩·埃德斯特罗姆(BjörnEdström)的MP3解码器也是必不可少的阅读材料,尽管它与我们使用留声机的工作并不完全相关。)
对于我们当前的目的,我们需要了解一些事情:
- MP3文件由一系列帧组成。 每帧包含1152个样本 。 通常,采样率是44.1kHz或48kHz,使帧持续约1/40秒。
- 每个帧都有一个帧头,后跟“主数据”。
- 帧头长度为四个字节(32位),并包含有关编码格式,采样率,比特率,通道数以及其他各种信息。 每个标头都以11个开头(如果您不支持MPEG版本2.5,而Phonograph当前不支持,则以12个开头),这被称为帧同步 ,很容易在大量二进制数据中进行识别,尽管有时会得到误报。
- 其中一些东西从一帧改变到下一帧,这意味着您不能仅仅寻找相同的四字节序列来标识帧头。 但是有些事情没有,这使得消除帧同步检查中的误报成为可能。
知道了所有这些之后,我们可以一次扫描一个字节,直到找到一个帧同步头为止,通过检查帧的元数据相对于采样率是否与我们的参考头(遇到的第一个)相匹配,消除了误报。等等,然后增加一个帧计数器。
一旦知道了多少帧,就将该数字乘以1152,然后除以采样率(从参考标题中知道),以确定剪辑的确切持续时间。
出乎意料的是,所有这一切都在几分之一毫秒之内发生,这意味着页面将继续平稳运行,而用户则是最明智的选择。
不过实话说
对于像播放声音这样简单的事情,这些都是疯狂的长度。 坚持要播放的用户手势不会完成任何事情,除了人为地限制Web相对于本机应用程序的功能。 它没有任何规格,没有达到其既定目标,只会给开发人员和用户带来更糟糕的体验。
浏览器制造商应该接受这是一个愚蠢的错误,并更改移动浏览器的行为以匹配桌面浏览器。
留声机正在进行中
我们发现它在Safari和Chrome(在移动浏览器使用中占主导地位)上非常强大。 Firefox和Opera是另外一回事,因为它们显然都拒绝解码mp3数据。
由于我们计划在将来的项目中使用它,因此我们将继续努力解决这些问题。 但是与此同时,我们希望获得更多反馈和错误报告,因此,如果您最终在项目中使用了留声机,请告诉我们您的情况。