一、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>
二、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方法来设置,
如上图所示,我们可以看到,通过设置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
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导出一遍,选取小的即可。另外,如果提供的文件音量大小不相同,注意在压缩时调整一下。