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