Cocos2dx提供的音频库位于CocosDenshion中,其接口由SimpleAudioEngine定义,提供了基本的背景音乐和音效播放。
SimpleAudioEngine的实现是夸平台的, 在windows平台上由mci相关API实现; 在android平台上透过JNI,调用android sdk 中的AudioPlayer实现;而在IOS平台上由Cocoa sdk里的Core-Audio实现。但SimpleAudioEngine并不适用于大部分游戏情境,它在Android上的实现需要直接把音乐音乐文件路径直接传给AudioPlayer---会导致音乐文件不可以打进资源包;它是一次性解析一整个音乐文件,导致音乐播放会出现卡顿现象,它的更新是主线程更新,切换场景会导致音乐卡顿而音乐音效密集的时候会阻塞逻辑线程等等。
所以我们不得不重新实现自己的音效系统,然后目前并无全平台统一的音效协议,即使 如PC和IOS同时支持OPENAL ,但其对API的支持程度也是不完全相同。
一、各平台对音效的支持程度
Android:
在Android2.3以前,NDK无音效API支持,在2.3以后,支持OPENSL音效API,提供硬件解码和播放
Android SDK的Media库支持包括AudioPlayer ,AudioManager在内的一系列音效播放、处理、录音功能
OpenAL-soft(项目地址:https://github.com/AerialX/openal-soft-android)提供了OPENAL的部分实现,不支持OPENAL录音相关的API,同时其API也不是线程安全的。
IOS:
部分的支持OPENAL,不支持OPENAL录音相关的API
Cocoa SDK里的Core Audio提供了完善的音乐解码、播放、录音和处理相关的API,提供硬件解码播放和处理功能
PC:
完整的OPENAL支持
DX支持的硬件解码音乐播放
于是最终我们选择了OpenAL,因为在有OpenAL-soft的支持下,它可以使我们以最少的代码横跨所有目标平台,其实现出已满足我们对音效API的需求。
音乐文件的编码我们选择了OGG,同样品质的ogg,其大小和mp3类似,它没有mp3的版权问题的。
音效我们则选择了wav格式,因为wav格式易于解码和保存。
二、音效系统的需求与架构
我们的音效系统完整需求如下:
1.音效系统对使用者来说实现平台无关化,同时支持Android ,IOS 和 Windows三平台
2.易于扩展的音乐格式支持,在所有平台上至少同时支持一种音乐和音效格式
3.支持一边解码一边播放
4.支持从自定义的资源包里读取音乐音效数据
5.在任何时候都不阻塞逻辑线程的运行
6.音乐音效在切换UI和场景的时候保持平滑不出现卡顿现象
7.支持音乐切换的淡入淡出
需求1只需采用基于接口编程就可以满足,需求2则把音乐解码器单独提出接口就可以满足,需求3通过AudioBuffer定期去解码器读取数据实现,而4则只需要音效系统可以从内存中解码音乐。需求5,6都在暗示我们需要一个单独的音效线程,来实现和主线程无关的音乐播放。需求7则可以通过一个简单的淡入淡出音量动画进行实现。
完整的音效系统架构如下:
对逻辑层来说,使用音效系统只需获取AudioSystem对象实例,然后调用playSound或playMusic即可,主线程调用play*只把播放参数推入待播放队列,后续的音效线程更新时从队列取出待播放的音乐音效进行播放。Play*接口定义如下:
//播放音效 //filename ,播放的资源名 //speed ,播放速度 virtual u32 playSound(const char* filename,float speed=1.f) = 0 ; //播放音乐 //filename ,播放的资源名 //speed ,播放速度 //offset , 播放起始位置 //fade ,是否淡入淡出 //repeatCount ,循环次数 virtual u32 playMusic(const char* filename,float speed = 1.f ,u32 offset =0 ,bool fade=true,u32 repeatCount=AudioRepeatInfinite) = 0 ;
三、平台相关的库该如何编译
1. OpenAL-soft
OpenAL-soft移植到android,需要自己编写android.mk和application.mk,把openal-soft编译成静态库(.a),
LOCAL_SRC_FILES := \ $(OPENAL_DIR)/Alc/android.c \ $(OPENAL_DIR)/OpenAL32/alAuxEffectSlot.c \ $(OPENAL_DIR)/OpenAL32/alBuffer.c \ $(OPENAL_DIR)/OpenAL32/alDatabuffer.c \ $(OPENAL_DIR)/OpenAL32/alEffect.c \ $(OPENAL_DIR)/OpenAL32/alError.c \ $(OPENAL_DIR)/OpenAL32/alExtension.c \ $(OPENAL_DIR)/OpenAL32/alFilter.c \ $(OPENAL_DIR)/OpenAL32/alListener.c \ $(OPENAL_DIR)/OpenAL32/alSource.c \ $(OPENAL_DIR)/OpenAL32/alState.c \ $(OPENAL_DIR)/OpenAL32/alThunk.c \ $(OPENAL_DIR)/Alc/ALc.c \ $(OPENAL_DIR)/Alc/alcConfig.c \ $(OPENAL_DIR)/Alc/alcEcho.c \ $(OPENAL_DIR)/Alc/alcModulator.c \ $(OPENAL_DIR)/Alc/alcReverb.c \ $(OPENAL_DIR)/Alc/alcRing.c \ $(OPENAL_DIR)/Alc/alcThread.c \ $(OPENAL_DIR)/Alc/ALu.c \ $(OPENAL_DIR)/Alc/bs2b.c \ $(OPENAL_DIR)/Alc/null.c \ $(OPENAL_DIR)/Alc/panning.c \ $(OPENAL_DIR)/Alc/mixer.c \ $(OPENAL_DIR)/Alc/audiotrack.c \
include $(BUILD_STATIC_LIBRARY)
同时要注意的是,OpenAL-soft的android.c里的JNI_OnLoad和cocos2dx会相冲突,需要把android.c里的JNIonLoad改名(如改为openal_jni_onLoad),并在cocos2dx生成工程main.cpp里的JNI_OnLoad里调用OpenAL-soft的相关函数(如openal_jni_onLoad).
2.libogg 和 libvorbis
libogg在xcode下建立ogg的静态库工程,添加bitwise.c和framing.c到工程内,并在游戏工程添加此工程依赖。在android下同样要自己建立android.mk编译为.a,其android.mk内容如下:
LOCAL_PATH := $(call my-dir)/../ include $(CLEAR_VARS) LOCAL_MODULE := ogg LOCAL_SRC_FILES := $(LOCAL_PATH)/src/bitwise.c \ $(LOCAL_PATH)/src/framing.c LOCAL_CFLAGS += -I$(LOCAL_PATH)/include -ffast-math -fsigned-char ifeq ($(TARGET_ARCH),arm) LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp endif include $(BUILD_STATIC_LIBRARY
libvorbis和libogg一样,在xcode下建立vorbis工程,并加入相应.c文件到工程内(哪些.c要加入工程见下文的android.mk内容),在android下同样建议android.mk,内容如下:
1 LOCAL_PATH := $(call my-dir)/../ 2 3 include $(CLEAR_VARS) 4 5 LOCAL_CFLAGS += -I$(LOCAL_PATH)/../libogg-1.3.1/include/ 6 LOCAL_CFLAGS += -I$(LOCAL_PATH)/lib/ 7 LOCAL_CFLAGS += -I$(LOCAL_PATH)/lib/modes/ 8 LOCAL_CFLAGS += -I$(LOCAL_PATH)/lib/books/ 9 LOCAL_CFLAGS += -I$(LOCAL_PATH)/include -ffast-math -fsigned-char 10 ifeq ($(TARGET_ARCH),arm) 11 LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp 12 endif 13 14 LOCAL_SRC_FILES := $(LOCAL_PATH)/lib/analysis.c \ 15 $(LOCAL_PATH)/lib/bitrate.c \ 16 $(LOCAL_PATH)/lib/block.c \ 17 $(LOCAL_PATH)/lib/codebook.c \ 18 $(LOCAL_PATH)/lib/envelope.c \ 19 $(LOCAL_PATH)/lib/floor0.c \ 20 $(LOCAL_PATH)/lib/floor1.c \ 21 $(LOCAL_PATH)/lib/info.c \ 22 $(LOCAL_PATH)/lib/lookup.c \ 23 $(LOCAL_PATH)/lib/lpc.c \ 24 $(LOCAL_PATH)/lib/lsp.c \ 25 $(LOCAL_PATH)/lib/mapping0.c \ 26 $(LOCAL_PATH)/lib/mdct.c \ 27 $(LOCAL_PATH)/lib/psy.c \ 28 $(LOCAL_PATH)/lib/registry.c \ 29 $(LOCAL_PATH)/lib/res0.c \ 30 $(LOCAL_PATH)/lib/sharedbook.c \ 31 $(LOCAL_PATH)/lib/smallft.c \ 32 $(LOCAL_PATH)/lib/synthesis.c \ 33 $(LOCAL_PATH)/lib/vorbisenc.c \ 34 $(LOCAL_PATH)/lib/vorbisfile.c \ 35 $(LOCAL_PATH)/lib/window.c \ 36 37 LOCAL_MODULE := vorbis 38 39 include $(BUILD_STATIC_LIBRARY