cocos2dx音效的实现分析:
上面那个图是cocos2dx音效部分的实现结构图,总体的思想是:
使用同一个SimpleAudioEngine.h头文件,然后在不同平台下对应不同
的实现文件,不同平台编译不同的实现文件,这就要求这个类的函数定义
成员在各个平台下统一。其实这也可以理解为一种跨平台的实现方式。
这里分析android部分:
- 1、预加载背景音乐
- void SimpleAudioEngine::preloadBackgroundMusic(const char* pszFilePath)
- {
- std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath);
- preloadBackgroundMusicJNI(fullPath.c_str());
- }
- -->>
- //得到音乐文件的路径,如果是包里的文件,则去掉assets/前缀
- static std::string getFullPathWithoutAssetsPrefix(const char* pszFilename)
- {
- // Changing file path to full path
- //获取文件的路径,以后有时间会分析下CCFileUtils这个类的实现
- std::string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename(pszFilename);
- // Removing `assets` since it isn't needed for the API of playing sound.
- size_t pos = fullPath.find("assets/");
- if (pos == 0)
- {
- fullPath = fullPath.substr(strlen("assets/"));
- }
- return fullPath;
- }
- -->>
- preloadBackgroundMusicJNI:
- //这里其实就是调用jni的方法:
- void preloadBackgroundMusicJNI(const char *path)
- {
- // void playBackgroundMusic(String,boolean)
- JniMethodInfo methodInfo;
- if (! getStaticMethodInfo(methodInfo,"preloadBackgroundMusic","(Ljava/lang/String;)V"))
- {
- return;
- }
- jstring stringArg = methodInfo.env->NewStringUTF(path);
- methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID,stringArg);
- methodInfo.env->DeleteLocalRef(stringArg);
- methodInfo.env->DeleteLocalRef(methodInfo.classID);
- }
- -->>java端的代码:
- public static void preloadBackgroundMusic(final String pPath) {
- Cocos2dxHelper.sCocos2dMusic.preloadBackgroundMusic(pPath);
- }
- -->>
- public void preloadBackgroundMusic(final String pPath) {
- if ((this.mCurrentPath == null) || (!this.mCurrentPath.equals(pPath))) {
- // preload new background music
- // release old resource and create a new one
- if (this.mBackgroundMediaPlayer != null) {
- this.mBackgroundMediaPlayer.release();
- }
- this.mBackgroundMediaPlayer = this.createMediaplayer(pPath);
- // record the path
- this.mCurrentPath = pPath;
- }
- }
- -->>
- private MediaPlayer createMediaplayer(final String pPath) {
- //其实就是创建了一个android下的MediaPlayer媒体播放实例,后面的播放,暂停之类
- //的,都是这个实例去控制。
- MediaPlayer mediaPlayer = new MediaPlayer();
- try {
- if (pPath.startsWith("/")) {
- final FileInputStream fis = new FileInputStream(pPath);
- mediaPlayer.setDataSource(fis.getFD());
- fis.close();
- } else {
- final AssetFileDescriptor assetFileDescritor = this.mContext.getAssets().openFd(pPath);
- mediaPlayer.setDataSource(assetFileDescritor.getFileDescriptor(),assetFileDescritor.getStartOffset(),assetFileDescritor.getLength());
- }
- mediaPlayer.prepare();
- mediaPlayer.setVolume(this.mLeftVolume,this.mRightVolume);
- } catch (final Exception e) {
- mediaPlayer = null;
- Log.e(Cocos2dxMusic.TAG,"error: " + e.getMessage(),e);
- }
- return mediaPlayer;
- }
- -->>播放背景音乐,这里可以不用预先加载,就直接播放。
- void SimpleAudioEngine::playBackgroundMusic(const char* pszFilePath,bool bLoop)
- {
- std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath);
- playBackgroundMusicJNI(fullPath.c_str(),bLoop);
- }
- 我在(1)已经分析了一些东西,这里接着分析,这一篇我们主要分析背景音乐文件的播放,
- 还是基于android平台:
- 1、
- 这里只是背景音乐的预加载,为什么要进行预加载呢?
- 主要是加载音乐文件是比较耗时的,如果我们没有预加载就直接播放也是可以的,
- 但是会有一定的延时,因为如果没有预加载,就直接播放,也是会先进行加载音乐文件,
- 然后进行播放。
- void SimpleAudioEngine::preloadBackgroundMusic(const char* pszFilePath)
- {
- std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath);
- preloadBackgroundMusicJNI(fullPath.c_str());
- }
- 其实加载背景音乐最终调用android端:
- public void preloadBackgroundMusic(final String pPath) {
- if ((this.mCurrentPath == null) || (!this.mCurrentPath.equals(pPath))) {
- // preload new background music
- // release old resource and create a new one
- // 如果我们播放的是一个新的背景音乐文件,那么我们需要先释放旧的播放器,然后创建一个新的
- // Releases resources associated with this MediaPlayer object.
- if (this.mBackgroundMediaPlayer != null) {
- this.mBackgroundMediaPlayer.release();
- }
- //创建一个播放器即MediaPlayer类的实例
- this.mBackgroundMediaPlayer = this.createMediaplayer(pPath);
- // record the path
- // 记录当前播放的背景音乐文件,因为下次如果播放的是同一个音乐
- // 文件,那么我们就可以直接进行播放了,不用再重新创建MediaPlayer类的实例
- this.mCurrentPath = pPath;
- }
- }
- ----->>>
- /**
- * create mediaplayer for music
- *
- * @param pPath
- * the pPath relative to assets
- * @return
- */
- private MediaPlayer createMediaplayer(final String pPath) {
- MediaPlayer mediaPlayer = new MediaPlayer();
- try {
- //对绝对路径和包里的路径进行区分处理,当最终的目的就是设置播放源
- if (pPath.startsWith("/")) {
- final FileInputStream fis = new FileInputStream(pPath);
- mediaPlayer.setDataSource(fis.getFD());
- fis.close();
- } else {
- final AssetFileDescriptor assetFileDescritor = this.mContext.getAssets().openFd(pPath);
- mediaPlayer.setDataSource(assetFileDescritor.getFileDescriptor(),assetFileDescritor.getLength());
- }
- //播放器前需要做些准备工作,这个只是android的api,不明白的话,查下文档。
- /**
- * Prepares the player for playback,synchronously.
- *
- * After setting the datasource and the display surface,you need to either
- * call prepare() or prepareAsync(). For files,it is OK to call prepare(),* which blocks until MediaPlayer is ready for playback.
- *
- * @throws IllegalStateException if it is called in an invalid state
- */
- mediaPlayer.prepare();
- //设置声音音量
- mediaPlayer.setVolume(this.mLeftVolume,e);
- }
- return mediaPlayer;
- }
- 2、
- 音乐播放函数
- //pszFilePath: 音乐文件名
- //bLoop: 是否循环播放,音乐文件我们一般设置为循环播放,看具体情况
- void SimpleAudioEngine::playBackgroundMusic(const char* pszFilePath,bLoop);
- }
- --->>> 最终都会调用到android端的playBackgroundMusic函数,并把文件路径,是否循环播放传进来
- public void playBackgroundMusic(final String path,final boolean isLoop) {
- if (mCurrentPath == null) {
- // it is the first time to play background music or end() was called
- // 如果以前没有播放过音乐文件,那么重新创建一个,上面的英文注释很清楚
- mBackgroundMediaPlayer = createMediaplayer(path);
- mCurrentPath = path;
- } else {
- if (!mCurrentPath.equals(path)) {
- // play new background music
- //如果这次播放的音乐文件和上次的不同,即是一个新的音乐文件,
- //那么就需要先释放掉旧的,然后创建一个新的。
- // release old resource and create a new one
- if (mBackgroundMediaPlayer != null) {
- mBackgroundMediaPlayer.release();
- }
- mBackgroundMediaPlayer = createMediaplayer(path);
- // record the path
- mCurrentPath = path;
- }
- }
- if (mBackgroundMediaPlayer == null) {
- Log.e(Cocos2dxMusic.TAG,"playBackgroundMusic: background media player is null");
- } else {
- try {
- // if the music is playing or paused,stop it
- // 对playing or paused,stop三种情况进行分别处理
- if (mPaused) {
- //如果是暂停状态,那么就把播放进度设置到0,然后开始。
- //这就意味着,如果我们调用了暂停,然后又调用play,那么
- //音乐将会从头开始播放,而不是从暂停的地方接着播放。
- /**
- * Seeks to specified time position.
- *
- * @param msec the offset in milliseconds from the start to seek to
- * @throws IllegalStateException if the internal player engine has not been
- * initialized
- */
- mBackgroundMediaPlayer.seekTo(0);
- /**
- * Starts(开始) or resumes playback(恢复播放). If playback had prevIoUsly been paused,* playback will continue from where it was paused. If playback had
- * been stopped,or never started before,playback will start at the
- * beginning.
- * start函数两个功能,一个是开始播放,一个是恢复播放
- * 1、如果stopped或者never started before(第一次开始),那么就从头开始播放
- * 2、如果paused即暂停,那么将会从暂停的地方接着播放。
- */
- mBackgroundMediaPlayer.start();
- } else if (mBackgroundMediaPlayer.isPlaying()) {
- //如果处于播放状态,则回到开始,从头播放
- mBackgroundMediaPlayer.seekTo(0);
- } else {
- //如果处于stop状态,则从新播放,上面已经说明了start函数的两个作用
- mBackgroundMediaPlayer.start();
- }
- /*
- 总结:其实对上面三种情况分别处理,最终达到的效果都是一样的,
- 那就是从头开始播放背景音乐文件。
- */
- //设置是否循环播放
- mBackgroundMediaPlayer.setLooping(isLoop);
- //mPaused 表示设为false,表示不处于暂停状态
- mPaused = false;
- //是否循环播放记录
- mIsLoop = isLoop;
- } catch (final Exception e) {
- Log.e(Cocos2dxMusic.TAG,"playBackgroundMusic: error state");
- }
- }
- }
- 3、总结:
- 从上面的分析我们可以知道,如果预先进行加载即先创建一个MediaPlayer,
- 那么我们播放时可以直接进行播放,如果我们我们没有提前进行预加载,
- 而是直接调用playBackgroundMusic函数,也可以进行播放,只不过会有一个创建
- MediaPlayer的过程,会有一些时间上的延时。
- 我在(2)已经分析了背景音乐文件的预加载preloadBackgroundMusic和播放playBackgroundMusic两个函数,
- 这里接着分析,还是基于android平台:
- 1、
- //暂停函数,用于音乐的暂停
- void SimpleAudioEngine::pauseBackgroundMusic()
- {
- //在SimpleAudioEngineJni.cpp源文件中定义
- pauseBackgroundMusicJNI();
- }
- //--pauseBackgroundMusicJNI--->>>
- void pauseBackgroundMusicJNI()
- {
- // void pauseBackgroundMusic()
- JniMethodInfo methodInfo;
- if (! getStaticMethodInfo(methodInfo,"pauseBackgroundMusic","()V"))
- {
- return;
- }
- //通过jni调用java端的函数,调用的是Cocos2dxHelper类中的
- /*
- public static void pauseBackgroundMusic() {
- Cocos2dxHelper.sCocos2dMusic.pauseBackgroundMusic();
- }
- */
- methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID);
- methodInfo.env->DeleteLocalRef(methodInfo.classID);
- }
- 最终调用的是Cocos2dxMusic类中的
- public void pauseBackgroundMusic() {
- //mBackgroundMediaPlayer在(2)中有分析过,创建的MediaPlayer实例
- if (this.mBackgroundMediaPlayer != null && this.mBackgroundMediaPlayer.isPlaying()) {
- this.mBackgroundMediaPlayer.pause();
- this.mPaused = true; //是否暂停标志
- }
- }
- 2、
- 恢复播放
- void SimpleAudioEngine::resumeBackgroundMusic()
- {
- resumeBackgroundMusicJNI();
- }
- 其实和上面暂停的调用过程是一样的,就不分析了,直接进入java端看最终调用的函数,public void resumeBackgroundMusic() {
- //这里只有处于暂停状态时即mPaused变量为true时,才会接着
- //上次播放的位置开始播放
- if (this.mBackgroundMediaPlayer != null && this.mPaused) {
- this.mBackgroundMediaPlayer.start();
- this.mPaused = false; //把暂停标志位设置false
- }
- }
- 3、
- 从头开始播放音乐文件
- void rewindBackgroundMusicJNI()
- {
- // void rewindBackgroundMusic()
- JniMethodInfo methodInfo;
- if (! getStaticMethodInfo(methodInfo,"rewindBackgroundMusic","()V"))
- {
- return;
- }
- methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID);
- methodInfo.env->DeleteLocalRef(methodInfo.classID);
- }
- --->>//java端函数
- //这个函数最终也是会调用playBackgroundMusic函数,但是和playBackgroundMusic有一点不同,
- //playBackgroundMusic需要传入音乐文件名,可以播放和上一次一样的音乐也可以和是上一次不一样的
- //音乐,但是rewindBackgroundMusic函数只有在mBackgroundMediaPlayer不为null时才执行,
- //也就是必须播放过音乐,且播放的是上次播放的音乐,只不过这次是从头开始播放
- public void rewindBackgroundMusic() {
- if (this.mBackgroundMediaPlayer != null) {
- playBackgroundMusic(mCurrentPath,mIsLoop);
- }
- }
- 4、停止播放音乐文件
- void stopBackgroundMusicJNI()
- {
- // void stopBackgroundMusic()
- JniMethodInfo methodInfo;
- if (! getStaticMethodInfo(methodInfo,"stopBackgroundMusic",methodInfo.methodID);
- methodInfo.env->DeleteLocalRef(methodInfo.classID);
- }
- --->>>//java端函数:
- public void stopBackgroundMusic() {
- if (this.mBackgroundMediaPlayer != null) {
- mBackgroundMediaPlayer.release();
- //不太明白这里为什么有从新创建了MediaPlayer实例
- //可能是一些特殊情况下会出现问题?
- mBackgroundMediaPlayer = createMediaplayer(mCurrentPath);
- // should set the state,if not,the following sequence will be error
- // play -> pause -> stop -> resume
- //为什么设置mPaused标志,直接看上面的英文注释
- this.mPaused = false;
- }
- }
- 5、
- 返回是否处于播放状态
- bool isBackgroundMusicPlayingJNI()
- {
- // boolean rewindBackgroundMusic()
- JniMethodInfo methodInfo;
- jboolean ret = false;
- if (! getStaticMethodInfo(methodInfo,"isBackgroundMusicPlaying","()Z"))
- {
- return ret;
- }
- ret = methodInfo.env->CallStaticBooleanMethod(methodInfo.classID,methodInfo.methodID);
- methodInfo.env->DeleteLocalRef(methodInfo.classID);
- return ret;
- }
- --->>>//java端函数,没什么东西
- public boolean isBackgroundMusicPlaying() {
- boolean ret = false;
- if (this.mBackgroundMediaPlayer == null) {
- ret = false;
- } else {
- ret = this.mBackgroundMediaPlayer.isPlaying();
- }
- return ret;
- }
- 6、获取播放声音音量值
- float getBackgroundMusicVolumeJNI()
- {
- // float getBackgroundMusicVolume()
- JniMethodInfo methodInfo;
- jfloat ret = -1.0;
- if (! getStaticMethodInfo(methodInfo,"getBackgroundMusicVolume","()F"))
- {
- return ret;
- }
- ret = methodInfo.env->CallStaticFloatMethod(methodInfo.classID,methodInfo.methodID);
- methodInfo.env->DeleteLocalRef(methodInfo.classID);
- return ret;
- }
- ------->>>//java
- public float getBackgroundVolume() {
- if (this.mBackgroundMediaPlayer != null) {
- return (this.mLeftVolume + this.mRightVolume) / 2;
- } else {
- return 0.0f;
- }
- }
- 7、设置声音音量值
- void setBackgroundMusicVolumeJNI(float volume)
- {
- // void setBackgroundMusicVolume()
- JniMethodInfo methodInfo;
- if (! getStaticMethodInfo(methodInfo,"setBackgroundMusicVolume","(F)V"))
- {
- return ;
- }
- methodInfo.env->CallStaticVoidMethod(methodInfo.classID,volume);
- methodInfo.env->DeleteLocalRef(methodInfo.classID);
- }
- ---->>>java
- public void setBackgroundVolume(float pVolume) {
- if (pVolume < 0.0f) {
- pVolume = 0.0f;
- }
- if (pVolume > 1.0f) {
- pVolume = 1.0f;
- }
- this.mLeftVolume = this.mRightVolume = pVolume;
- if (this.mBackgroundMediaPlayer != null) {
- this.mBackgroundMediaPlayer.setVolume(this.mLeftVolume,this.mRightVolume);
- }
- }
- 8、
- end函数,这个一般在退出游戏是调用,关掉所有的音乐和音效。
- void endJNI()
- {
- // void end()
- JniMethodInfo methodInfo;
- if (! getStaticMethodInfo(methodInfo,"end","()V"))
- {
- return ;
- }
- methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID);
- methodInfo.env->DeleteLocalRef(methodInfo.classID);
- }
- --->>>//java端函数
- public static void end() {
- Cocos2dxHelper.sCocos2dMusic.end(); //背景音乐文件的处理
- Cocos2dxHelper.sCocos2dSound.end();
- }
- ---->>>>/////背景音乐文件的处理
- public void end() {
- if (this.mBackgroundMediaPlayer != null) {
- this.mBackgroundMediaPlayer.release();
- }
- //把所有的变量恢复初始值
- /*
- private void initData() {
- this.mLeftVolume = 0.5f;
- this.mRightVolume = 0.5f;
- this.mBackgroundMediaPlayer = null;
- this.mPaused = false;
- this.mCurrentPath = null;
- }
- */
- this.initData();
- }