Laya Audio WebAudio

一、html5 audio标签

参考
html audio标签
使用HTML5的Audio标签打造WEB音频播放器
打造属于自己的音乐播放器 HTML5之audio标签

<!DOCTYPE HTML>
<html>
<body>

<audio src="/i/horse.ogg" controls="controls" autoplay="autoplay" loop = "loop">
Your browser does not support the audio element.
</audio>

</body>
</html>

属性:
autoplay 如果出现该属性,则音频在就绪后马上播放。
controls 如果出现该属性,则向用户显示控件,比如播放按钮。
loop 如果出现该属性,则每当音频结束时重新开始播放。
muted 规定视频输出应该被静音。
preload 如果出现该属性,则音频在页面加载时进行加载,并预备播放。如果使用 "autoplay",则忽略该属性。
src 要播放的音频的 URL。

为了兼容性,一般会使用3个source标签

<audio controls="controls">  
   <source src="music.ogg" /> 
   <source src="music.mp3" /> 
   <source src="music.wav" /> 
</audio> 
HTML5浏览器和音频格式兼容性
不同浏览器音频空间外观也不一样
二、web audio

参考
利用HTML5 Web Audio API给网页JS交互增加声音
开大你的音响,感受HTML5 Audio API带来的视听盛宴
大话Web-Audio-Api
5个操作HTML5 Audio的库与API
首先务必要弄清这一点,本文这里所说的HTML5 Web Audio API和HTML5 <audio>元素完全不是一个东西,其体量也完全不是一个等级的,HTML5 Web Audio API接口的丰富程度和体量可以和HTML canvas API相提并论,其能实现的功能也非常令人瞠目。

HTML5 Web Audio API可以让我们无中生有创造声音,而且是各种音调的声音,换句话说,我们通过JavaScript就会创建一个完整的音乐出来

三、HTML5游戏中的问题

HTML5游戏引擎中音频的播放策略
HTML5 API---使用WebAudio API播放音频文件

随着HTML5 API的不断丰富和浏览器对html5支持的不断完善,基于Html5开发的游戏引擎也慢慢流行,Web开发者基于HTML5游戏引擎开发的游戏可以很好的实现跨平台功能,例如游戏开发者基于Android开发一款游戏,马上就能运行在iOS设备上,这大大减少了游戏的开发周期,对开发者来说是最大的实惠。

在游戏引擎中,音乐的播放是必须的,目前我们见到的游戏中几乎都有背景音乐,那么如何在HTML5游戏引擎中实现音频文件的播放呢?首先我们想到的是HTML5提供的audio元素,现在普遍的做法是在HTML文件中动态插入一个audio元素,再把audio元素的src属性设置成需要播放文件的路径,然后调用play方法即可完成音频的播放。

在PC浏览器中,上述方案可以正常工作,但是在移动设备android和ios上可能会存在下面两个问题。

  • 移动设备上的audio元素在播放前需要一个触屏消息,也就是说无论在audio元素中设置了autoplay或是明确的调用play方法,都不能立即播放音频文件,必须等待用户触摸屏幕后才能播放。目前Android设备上的Chrome浏览器和原生的Android浏览器都有该限制,iOS也有该限制,目的是为了节省移动设备的资源。chrome浏览器可以使用chrome://flags/#disable-gesture-requirement-for-media-playback解决,参考关于移动端audio自动播放问题。也可以做一个音频按钮,引导用户选择需不需要背景音乐,然后就触发了用户操作。

  • 移动设备上的某些浏览器暂时不支持多个音频文件同时播放,这对游戏开发者是不能接受的,因为游戏一般都有背景音乐,另外在游戏的过程中,也同时需要触发其他音乐。目前Android设备上原生的Android浏览器就会有该限制。

面对上述两个问题,在游戏引擎中依靠简单的插入audio元素来播放音乐的方案,在移动设备上就行不通了,所以一些流行的HTML5游戏引擎就采用了另外一种方案,当浏览器支持WebAudio API时,就采用WebAudio替换audio元素来播放音频文件。比如Cocos2d-html5和Construct2,前者是开源的游戏引擎,在国内比较流行,后者是需要收费的,在国外应用较广。

var context = new window.webkitAudioContext();  
var source = null;  
var audioBuffer = null;  
function stopSound() {  
    if (source) {  
        source.noteOff(0); //立即停止  
    }  
}  
function playSound() {  
    source = context.createBufferSource();  
    source.buffer = audioBuffer;  
    source.loop = true;  
    source.connect(context.destination);  
    source.noteOn(0); //立即播放  
}  
function initSound(arrayBuffer) {  
    context.decodeAudioData(arrayBuffer, function(buffer) { //解码成功时的回调函数  
        audioBuffer = buffer;  
        playSound();  
    }, function(e) { //解码出错时的回调函数  
        console.log('Error decoding file', e);  
    });  
}  
function loadAudioFile(url) {  
    var xhr = new XMLHttpRequest(); //通过XHR下载音频文件  
    xhr.open('GET', url, true);  
    xhr.responseType = 'arraybuffer';  
    xhr.onload = function(e) { //下载完成  
        initSound(this.response);  
    };  
    xhr.send();  
}  
loadSoundFile('demo-audio.mp3');  

上面的实例中,先通过XMLHttpRequest API从服务器上下载音频文件到本地,在下载完毕后,调用iniSound函数,该函数调用WebAudio中的解码函数decodeAudioData对下载的数据(当前存储为ArrayBuffer)进行解码,如果平台不支持音频文件的解码,将调用出错回调函数,如果解码成功就会调用成功的回调函数。现在假设解码成功,在成功的解码函数中,调用playSound函数去播放解码后的音频文件。

使用WebAudio播放音频文件的效率问题

前面介绍了如何使用WebAudio来播放音频文件,但是需要注意的是不要轻易采用WebAudio的该功能,因为当音频文件较大时,可能会影响程序的执行效率。首先,如果我们在程序中采用XMLHttpRequest去下载文件时,这是一个比较耗时的操作,具体的时间取决于当前的网络环境和文件的大小,尽管程序中采用异步的下载方式,但是同样会让音频的播放延迟。其次,程序需要调用WebAudio的decodeAudioData函数去解码整个音频文件,这里需要注意的是它需要一次性解码整个文件后,才会触发成功的回调函数,程序才能开始播放音频文件,这又一次的增加了音频文件播放的延迟,另外,由于整个文件的一次性解码,整个解码前和解码后的文件都同时存放在内存中,这也引起了内存的巨大开销(相比采用audio元素播放时,因为audio元素是一边解码一边播放)。此时可能有朋友会质疑decodeAudioData API的实现有问题,其实该函数是为解码比较短小的声音文件而设计的,另外由于WebAudio对音频的延时性特别关注,所以为了较少声音的延时,在音效处理前要求把需要处理的音频文件装载进内存。所以如果需要使用WebAudio播放文件,又比较关注效率问题时,建议把音频文件的大小缩小一些,或者分解成若干小的文件再分别加载解码播放。

四、laya HTML5音乐与音效的播放

参考音频--播放演示
HTML5的音频播放,在当前有两种主流的方式,一种是Audio标签播放,另一种是WebAudio二进制播放。

​ Audio属于dom元素,带有ui界面,在移动端Audio属于边下载边播放,适合声音文件比较大的文件,但是Audio在移动端会有手势的限制,gesture-requirement-for-media-playback属性表明必须有用户的手势操作才可以播放。

​ WebAudio是一种新的声音播放形式,可以加载多个声音进行合成,他是通过二进制文件解码成浏览器支持的格式进行播放。而且用这个接口甚至可以实现音频普的动画效果,让声音有了合成的功能。

​ 音乐与音效作为游戏中常用的基础元素,LayaAir引擎封装了WebAudio与Audio,在支持WebAudio的浏览器上,优先使用WebAudio,在不支持WebAudio的浏览器上使用Audio,最大化兼容所有浏览器对音频格式的支持,让开发者可以更加方便的,通过调用laya.media.SoundManager API接口就可以直接播放音频。

1.音乐与音效的应用区别

  • 音乐:是指游戏用的背景音乐。采用laya.media.SoundManager音频管理类中的playMusic方法进行播放,由于是背景音乐,playMusic方法只能同时播放一个音频文件。

  • 音效:采用的是laya.media.SoundManager音频管理类中的playSound方法,允许同时播放多个音频文件。

2.音频的兼容性准备
​ 由于音频播放问题的各个浏览器兼容性不同,在开始应用前,我们要做好前期的兼容准备。
(1)使用“格式工厂”音频文件转换工具。选择 44100Hz,96kbps 进行转换。
(2)音频文件尽量小,不仅仅是带宽的限制,还有浏览器音频解码的效率问题。
注意:打包APP有声音格式限制,请参考(LayaNative其他设置声音)

3.音频音量的控制
​ 声音音量的控制 可以通过laya.media.SoundManager音频管理类中的setSoundVolume方法来设置,

Paste_Image.png

如上图所示,我们可以看到,通过设置volume参数,可以有效控制url所对应声音文件的音量大小。

4.SoundManager
SoundManager 是一个声音管理类。提供了对背景音乐、音效的播放控制方法。 引擎默认有两套声音方案:WebAudio和H5Audio 播放音效,优先使用WebAudio播放声音,如果WebAudio不可用,则用H5Audio播放,H5Audio在部分机器上有兼容问题(比如不能混音,播放有延迟等)。 播放背景音乐,则使用H5Audio播放(使用WebAudio会增加特别大的内存,并且要等加载完毕后才能播放,有延迟) 建议背景音乐用mp3类型,音效用wav或者mp3类型(如果打包为app,音效只能用wav格式)。

  • autoStopMusic : Boolean
    失去焦点后是否自动停止背景音乐。原理参考SoundManager.as:
if (v) {
    Laya.stage.on(Event.BLUR, null, _stageOnBlur);
    Laya.stage.on(Event.FOCUS, null, _stageOnFocus);
    Laya.stage.on(Event.VISIBILITY_CHANGE, null, _visibilityChange);
}

引擎默认值为true,参考Laya.as的init方法:
SoundManager.autoStopMusic = true;

  • autoReleaseSound : Boolean = true
    音效播放后自动删除。
/**
 * 释放声音资源。
 * @param url   声音播放地址。
 */
public static function destroySound(url:String):void {
    var tSound:Sound = Laya.loader.getRes(url);
    if (tSound) {
        Loader.clearRes(url);
        tSound.dispose();
    }
}

5.useAudioMusic : Boolean = true 背景音乐使用Audio标签播放。
参考Browser.as

webAudioEnabled =/*[STATIC SAFE]*/ window["AudioContext"] ||
 window["webkitAudioContext"] || window["mozAudioContext"] ? true : false;
soundType =/*[STATIC SAFE]*/ webAudioEnabled ? "WEBAUDIOSOUND" : "AUDIOSOUND";
...
__JS__("Sound = Browser.webAudioEnabled?WebAudioSound:AudioSound;");
__JS__("if (Browser.webAudioEnabled) WebAudioSound.initWebAudio();");
AudioSound._initMusicAudio();
...
__JS__("SoundManager._soundClass=Sound;");

可以看出,优先使用WebAudioSound

/**
 * 播放音效。音效可以同时播放多个。
 * @param url           声音文件地址。
 * @param loops         循环次数,0表示无限循环。
 * @param complete      声音播放完成回调  Handler对象。
 * @param soundClass    使用哪个声音类进行播放,null表示自动选择。
 * @param startTime     声音播放起始时间。
 * @return SoundChannel对象,通过此对象可以对声音进行控制,以及获取声音信息。
 */
public static function playSound(url:String, loops:int = 1, complete:Handler = null, 
soundClass:Class = null, startTime:Number = 0):SoundChannel {
    ...
    var tSound:Sound = Laya.loader.getRes(url);
    if (!soundClass) soundClass = _soundClass;
    if (!tSound) {
        tSound = new soundClass();
        tSound.load(url);
        Loader.cacheRes(url, tSound);
    }
/**
 * 播放背景音乐。背景音乐同时只能播放一个,如果在播放背景音乐时再次调用本方法,
会先停止之前的背景音乐,再播发当前的背景音乐。
 * @param url       声音文件地址。
 * @param loops     循环次数,0表示无限循环。
 * @param complete  声音播放完成回调。
 * @param startTime 声音播放起始时间。
 * @return SoundChannel对象,通过此对象可以对声音进行控制,以及获取声音信息。
 */
public static function playMusic(url:String, loops:int = 0, 
complete:Handler = null, startTime:Number = 0):SoundChannel {
    url = URL.formatURL(url);
    _tMusic = url;
    if (_musicChannel) _musicChannel.stop();
    return _musicChannel = playSound(url, loops, complete, 
        useAudioMusic?AudioSound:null, startTime);
}
五、web audio在IOS上解锁

1.laya源码
类似的处理:iOS Safari not unlocking Web AudioContext on swipe

        /**
         * 用于播放解锁声音以及解决Ios9版本的内存释放
         */
        public static var _miniBuffer:* = ctx.createBuffer(1, 1, 22050);

        /**
         * 播放声音以解锁IOS的声音
         *
         */
        private static function _playEmptySound():void {
            if (ctx == null) {
                return;
            }
            var source:* = ctx.createBufferSource();
            source.buffer = _miniBuffer;
            source.connect(ctx.destination);
            source.start(0, 0, 0);
        }
        
        /**
         * 尝试解锁声音
         *
         */
        private static function _unlock():void {
            if (_unlocked) {
                return;
            }
            _playEmptySound();
            if (ctx.state == "running") {
                Browser.document.removeEventListener("mousedown", _unlock, true);
                Browser.document.removeEventListener("touchend", _unlock, true);
                _unlocked = true;
            }
        }
        ;
        
        public static function initWebAudio():void {
            if (ctx.state != "running") {
                _unlock(); // When played inside of a touch event, this will enable audio on iOS immediately.
                Browser.document.addEventListener("mousedown", _unlock, true);
                Browser.document.addEventListener("touchend", _unlock, true);
            }
        }

2.Unlock Web Audio in iOS 9 Safari

var ctx = null, usingWebAudio = true;

try {
  if (typeof AudioContext !== 'undefined') {
      ctx = new AudioContext();
  } else if (typeof webkitAudioContext !== 'undefined') {
      ctx = new webkitAudioContext();
  } else {
      usingWebAudio = false;
  }
} catch(e) {
    usingWebAudio = false;
}

// context state at this time is `undefined` in iOS8 Safari
if (usingWebAudio && ctx.state === 'suspended') {
  var resume = function () {
    ctx.resume();

    setTimeout(function () {
      if (ctx.state === 'running') {
        document.body.removeEventListener('touchend', resume, false);
      }
    }, 0);
  };

  document.body.addEventListener('touchend', resume, false);
}

3.使用了解锁后,还需要在HTML里添加一个audio标签才生效,目前不清楚原因。

<audio src="sounds/fish.mp3">
您的浏览器不支持 audio 标签。
</audio>

2017.11.13,1.7.12版本中,发现Broswer.as的init方法中多了一行AudioSound._initMusicAudio();

/**@private */
public static function _initMusicAudio():void
{
    if (_musicAudio) return;
    if (!_musicAudio) _musicAudio = Browser.createElement("audio") as Audio;
    if (!Render.isConchApp) {
        Browser.document.addEventListener("touchstart", _makeMusicOK);
    }
}

这个方法在之前是private的,可以看到注释还没有改掉……
检查了一下,是在1.7.10这个版本中添加上来的。但是测试了一下,audio标签还是播放不了背景音乐……

4.在android chrome上,直接在192.168.188.253:8080/index.html访问是可以播放webaudio的,但是通过iframe src="192.168.188.253:8080/index.html"却不行。经过搜索,是chrome禁止iframe中的跨域webaudio自动播放。参考Block Web Audio autoplay on cross origin iframes

为了解决此问题,参考Chrome 55 now requires audio to be unlocked by user gestures, fails playing audio on Android in iframe

WebAudioSound._playEmptySound=function(){
    if (WebAudioSound.ctx==null){
        return;
    };
    var source=WebAudioSound.ctx.createBufferSource();
    source.buffer=WebAudioSound._miniBuffer;
    source.connect(WebAudioSound.ctx.destination);
    source.start(0,0,0);
    //Hello Chrome 55!
    if (source.context.state === 'suspended') {
        source.context.resume();
    }
}
六、mp3文件体积压缩

项目同事提供的mp3文件体积太大,那么我们怎么压缩一下呢。参考使用 audacity/lame/ffmpeg 进行 mp3 文件瘦身手机游戏音频压缩
音频文件有这样几个基础指标:

1.采样率
单位 KHZ(千赫兹),48/44.1 为 CD 音质;22.05/24 为语音音质;8/12/11.025 为电话音质。如果只是为了瘦身,不必关注这个值。在瘦身的过程中一般不需要重采样(resample)。

2.比特率
单位 kbit/s(aka kbps,千字位每秒),极限音质使用 320;较好音质使用 128;普通音质使用64。我们在互联网下载到的大多数 mp3 音乐都是 128 kbps。见下方网易云音乐截图。

3.CBR/VBR/ABR
固定比特率、可变比特率、平均比特率。它们的比特率曲线见下图。

  • Constant Bit Rate 固定比特率对音乐中的所有部分使用固定的比特率编码。对于手机游戏来说不建议选择。
  • Variable Bit Rate 可变比特率通过对声音进行分析,对动态大的部分使用较大的比特率,否则采用较小的比特率。这种方式可以在音质类似的前提下降低文件大小。
  • Average Bit Rate 每隔50帧对声音进行一次分析,在这50帧中采用 VBR 的编码方式。这是 LAME 发明的方法,是对 CBR 和 VBR 的折衷,理论上音质比 VBR 略好,文件大小和 VBR 相当。

4.Mono/Stereo/Joint Stereo
单声道、立体声和联合立体声。

  • 单声道音频使用单条音轨保存声音信息,左右耳听到的声音完全相同,保存的信息量比立体声少二分之一。虽然使用单声道可以让文件变得更小,但目前的手机都支持立体声,为了让用户得到更好的体验,除非音源就是单声道,否则建议使用立体声。
  • 立体声使用两条音轨分别存储左耳和右耳听到的音频。
  • 联合立体声会利用两条音轨中的相似的内容进行压缩,因此文件会比立体声小。许多encoder 在进行大码率压缩(>256)的时候,会自动把联合立体声替换成立体声。

对于一般的非故事类手机游戏,我个人的建议是下面的值:

  • 背景音乐: 64kbps ABR 或 45~96kbps VBR,联合立体声。
    背景音乐一般比较长,文件也比较大,应该适当调低码率以降低文件大小。
  • 人声: 64kbps ABR 或 32~80kbps VBR,联合立体声。
    人声对高频音并不敏感。而且由于人耳对人声比较熟悉,可以比背景音乐的码率稍低。
  • 音效: 96 kbps ABR 或 60~128kbps VBR,联合立体声。
    音效文件长度都比较短,且对用户体验的影响较大。适当增加音效的码率会带来更好的游戏体验。

而故事类和音乐类游戏对音质的要求更高,不在本文讨论范围内。

目前我使用的是格式工厂,可以方便对整个文件夹处理。参数设置成64kbps ABR导出一遍,再设置成32~80kbps VBR导出一遍,选取小的即可。另外,如果提供的文件音量大小不相同,注意在压缩时调整一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,577评论 25 707
  • 早上,八点,图书馆。 三十五岁的路明非,推开大门,掏出黑卡,识别进入。 “权限,校长准许进入。”EVA传来冰冷的声...
    wl二哈阅读 318评论 1 0
  • 青峰科技19小时前快速排序算法是分治算法技术的一个实例,也称为分区交换排序。快速排序采用递归调用对元素进行排序,是...
    不二王1006阅读 617评论 0 50