cocos2dx 音频模块分析(1):背景音乐

前端之家收集整理的这篇文章主要介绍了cocos2dx 音频模块分析(1):背景音乐前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

cocos2dx音效的实现分析:


上面那个图是cocos2dx音效部分的实现结构图,总体的思想是:
使用同一个SimpleAudioEngine.h头文件,然后在不同平台下对应不同
的实现文件,不同平台编译不同的实现文件,这就要求这个类的函数定义
成员在各个平台下统一。其实这也可以理解为一种跨平台的实现方式。


这里分析android部分:

  1. 1预加载背景音乐
  2. void SimpleAudioEngine::preloadBackgroundMusic(const char* pszFilePath)
  3. {
  4. std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath);
  5. preloadBackgroundMusicJNI(fullPath.c_str());
  6. }
  7.  
  8. -->>
  9. //得到音乐文件的路径,如果是包里的文件,则去掉assets/前缀
  10. static std::string getFullPathWithoutAssetsPrefix(const char* pszFilename)
  11. {
  12. // Changing file path to full path
  13. //获取文件的路径,以后有时间会分析下CCFileUtils这个类的实现
  14. std::string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename(pszFilename);
  15. // Removing `assets` since it isn't needed for the API of playing sound.
  16. size_t pos = fullPath.find("assets/");
  17. if (pos == 0)
  18. {
  19. fullPath = fullPath.substr(strlen("assets/"));
  20. }
  21. return fullPath;
  22. }
  23.  
  24. -->>
  25. preloadBackgroundMusicJNI
  26. //这里其实就是调用jni的方法
  27. void preloadBackgroundMusicJNI(const char *path)
  28. {
  29. // void playBackgroundMusic(String,boolean)
  30. JniMethodInfo methodInfo;
  31. if (! getStaticMethodInfo(methodInfo,"preloadBackgroundMusic","(Ljava/lang/String;)V"))
  32. {
  33. return;
  34. }
  35. jstring stringArg = methodInfo.env->NewStringUTF(path);
  36. methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID,stringArg);
  37. methodInfo.env->DeleteLocalRef(stringArg);
  38. methodInfo.env->DeleteLocalRef(methodInfo.classID);
  39. }
  40. -->>java端的代码
  41. public static void preloadBackgroundMusic(final String pPath) {
  42. Cocos2dxHelper.sCocos2dMusic.preloadBackgroundMusic(pPath);
  43. }
  44. -->>
  45. public void preloadBackgroundMusic(final String pPath) {
  46. if ((this.mCurrentPath == null) || (!this.mCurrentPath.equals(pPath))) {
  47. // preload new background music
  48.  
  49. // release old resource and create a new one
  50. if (this.mBackgroundMediaPlayer != null) {
  51. this.mBackgroundMediaPlayer.release();
  52. }
  53.  
  54. this.mBackgroundMediaPlayer = this.createMediaplayer(pPath);
  55.  
  56. // record the path
  57. this.mCurrentPath = pPath;
  58. }
  59. }
  60. -->>
  61. private MediaPlayer createMediaplayer(final String pPath) {
  62. //其实就是创建了一个android下的MediaPlayer媒体播放实例,后面的播放,暂停之类
  63. //的,都是这个实例去控制。
  64. MediaPlayer mediaPlayer = new MediaPlayer();
  65.  
  66. try {
  67. if (pPath.startsWith("/")) {
  68. final FileInputStream fis = new FileInputStream(pPath);
  69. mediaPlayer.setDataSource(fis.getFD());
  70. fis.close();
  71. } else {
  72. final AssetFileDescriptor assetFileDescritor = this.mContext.getAssets().openFd(pPath);
  73. mediaPlayer.setDataSource(assetFileDescritor.getFileDescriptor(),assetFileDescritor.getStartOffset(),assetFileDescritor.getLength());
  74. }
  75.  
  76. mediaPlayer.prepare();
  77.  
  78. mediaPlayer.setVolume(this.mLeftVolume,this.mRightVolume);
  79. } catch (final Exception e) {
  80. mediaPlayer = null;
  81. Log.e(Cocos2dxMusic.TAG,"error: " + e.getMessage(),e);
  82. }
  83.  
  84. return mediaPlayer;
  85. }
  86.  
  87. -->>播放背景音乐,这里可以不用预先加载,就直接播放。
  88. void SimpleAudioEngine::playBackgroundMusic(const char* pszFilePath,bool bLoop)
  89. {
  90. std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath);
  91. playBackgroundMusicJNI(fullPath.c_str(),bLoop);
  92. }

  1. 我在(1)已经分析了一些东西,这里接着分析,这一篇我们主要分析背景音乐文件的播放,
  2. 还是基于android平台:
  3.  
  4. 1
  5. 这里只是背景音乐的预加载,为什么要进行预加载呢?
  6. 主要是加载音乐文件是比较耗时的,如果我们没有预加载就直接播放也是可以的,
  7. 但是会有一定的延时,因为如果没有预加载,就直接播放,也是会先进行加载音乐文件
  8. 然后进行播放。
  9. void SimpleAudioEngine::preloadBackgroundMusic(const char* pszFilePath)
  10. {
  11. std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath);
  12. preloadBackgroundMusicJNI(fullPath.c_str());
  13. }
  14.  
  15. 其实加载背景音乐最终调用android端:
  16. public void preloadBackgroundMusic(final String pPath) {
  17. if ((this.mCurrentPath == null) || (!this.mCurrentPath.equals(pPath))) {
  18. // preload new background music
  19.  
  20. // release old resource and create a new one
  21. // 如果我们播放的是一个新的背景音乐文件,那么我们需要先释放旧的播放器,然后创建一个新的
  22. // Releases resources associated with this MediaPlayer object.
  23. if (this.mBackgroundMediaPlayer != null) {
  24. this.mBackgroundMediaPlayer.release();
  25. }
  26. //创建一个播放器即MediaPlayer类的实例
  27. this.mBackgroundMediaPlayer = this.createMediaplayer(pPath);
  28.  
  29. // record the path
  30. // 记录当前播放的背景音乐文件,因为下次如果播放的是同一个音乐
  31. // 文件,那么我们就可以直接进行播放了,不用再重新创建MediaPlayer类的实例
  32. this.mCurrentPath = pPath;
  33. }
  34. }
  35.  
  36. ----->>>
  37. /**
  38. * create mediaplayer for music
  39. *
  40. * @param pPath
  41. * the pPath relative to assets
  42. * @return
  43. */
  44. private MediaPlayer createMediaplayer(final String pPath) {
  45. MediaPlayer mediaPlayer = new MediaPlayer();
  46.  
  47. try {
  48. //对绝对路径和包里的路径进行区分处理,当最终的目的就是设置播放源
  49. if (pPath.startsWith("/")) {
  50. final FileInputStream fis = new FileInputStream(pPath);
  51. mediaPlayer.setDataSource(fis.getFD());
  52. fis.close();
  53. } else {
  54. final AssetFileDescriptor assetFileDescritor = this.mContext.getAssets().openFd(pPath);
  55. mediaPlayer.setDataSource(assetFileDescritor.getFileDescriptor(),assetFileDescritor.getLength());
  56. }
  57. //播放器前需要做些准备工作,这个只是android的api,不明白的话,查下文档。
  58. /**
  59. * Prepares the player for playback,synchronously.
  60. *
  61. * After setting the datasource and the display surface,you need to either
  62. * call prepare() or prepareAsync(). For files,it is OK to call prepare(),* which blocks until MediaPlayer is ready for playback.
  63. *
  64. * @throws IllegalStateException if it is called in an invalid state
  65. */
  66. mediaPlayer.prepare();
  67. //设置声音音量
  68. mediaPlayer.setVolume(this.mLeftVolume,e);
  69. }
  70.  
  71. return mediaPlayer;
  72. }
  73.  
  74. 2
  75. 音乐播放函数
  76. //pszFilePath: 音乐文件
  77. //bLoop: 是否循环播放,音乐文件我们一般设置为循环播放,看具体情况
  78. void SimpleAudioEngine::playBackgroundMusic(const char* pszFilePath,bLoop);
  79. }
  80.  
  81. --->>> 最终都会调用android端的playBackgroundMusic函数,并把文件路径,是否循环播放传进来
  82. public void playBackgroundMusic(final String path,final boolean isLoop) {
  83. if (mCurrentPath == null) {
  84. // it is the first time to play background music or end() was called
  85. // 如果以前没有播放过音乐文件,那么重新创建一个,上面的英文注释很清楚
  86. mBackgroundMediaPlayer = createMediaplayer(path);
  87. mCurrentPath = path;
  88. } else {
  89. if (!mCurrentPath.equals(path)) {
  90. // play new background music
  91. //如果这次播放的音乐文件和上次的不同,即是一个新的音乐文件
  92. //那么就需要先释放掉旧的,然后创建一个新的。
  93. // release old resource and create a new one
  94. if (mBackgroundMediaPlayer != null) {
  95. mBackgroundMediaPlayer.release();
  96. }
  97. mBackgroundMediaPlayer = createMediaplayer(path);
  98.  
  99. // record the path
  100. mCurrentPath = path;
  101. }
  102. }
  103.  
  104. if (mBackgroundMediaPlayer == null) {
  105. Log.e(Cocos2dxMusic.TAG,"playBackgroundMusic: background media player is null");
  106. } else {
  107. try {
  108. // if the music is playing or paused,stop it
  109. // 对playing or paused,stop三种情况进行分别处理
  110. if (mPaused) {
  111. //如果是暂停状态,那么就把播放进度设置到0,然后开始。
  112. //这就意味着,如果我们调用了暂停,然后又调用play,那么
  113. //音乐将会从头开始播放,而不是从暂停的地方接着播放。
  114. /**
  115. * Seeks to specified time position.
  116. *
  117. * @param msec the offset in milliseconds from the start to seek to
  118. * @throws IllegalStateException if the internal player engine has not been
  119. * initialized
  120. */
  121. mBackgroundMediaPlayer.seekTo(0);
  122.  
  123. /**
  124. * Starts(开始) or resumes playback(恢复播放). If playback had prevIoUsly been paused,* playback will continue from where it was paused. If playback had
  125. * been stopped,or never started before,playback will start at the
  126. * beginning.
  127. * start函数两个功能,一个是开始播放,一个是恢复播放
  128. * 1、如果stopped或者never started before(第一次开始),那么就从头开始播放
  129. * 2、如果paused即暂停,那么将会从暂停的地方接着播放。
  130. */
  131. mBackgroundMediaPlayer.start();
  132. } else if (mBackgroundMediaPlayer.isPlaying()) {
  133. //如果处于播放状态,则回到开始,从头播放
  134. mBackgroundMediaPlayer.seekTo(0);
  135. } else {
  136. //如果处于stop状态,则从新播放,上面已经说明了start函数的两个作用
  137. mBackgroundMediaPlayer.start();
  138. }
  139. /*
  140. 总结:其实对上面三种情况分别处理,最终达到的效果都是一样的,
  141. 那就是从头开始播放背景音乐文件
  142. */
  143.  
  144. //设置是否循环播放
  145. mBackgroundMediaPlayer.setLooping(isLoop);
  146.  
  147. //mPaused 表示设为false,表示不处于暂停状态
  148. mPaused = false;
  149.  
  150. //是否循环播放记录
  151. mIsLoop = isLoop;
  152. } catch (final Exception e) {
  153. Log.e(Cocos2dxMusic.TAG,"playBackgroundMusic: error state");
  154. }
  155. }
  156. }
  157.  
  158. 3、总结:
  159. 从上面的分析我们可以知道,如果预先进行加载即先创建一个MediaPlayer
  160. 那么我们播放时可以直接进行播放,如果我们我们没有提前进行预加载
  161. 而是直接调用playBackgroundMusic函数,也可以进行播放,只不过会有一个创建
  162. MediaPlayer的过程,会有一些时间上的延时。

  1. 我在(2)已经分析了背景音乐文件预加载preloadBackgroundMusic和播放playBackgroundMusic两个函数
  2. 这里接着分析,还是基于android平台:
  3. 1
  4. //暂停函数,用于音乐的暂停
  5. void SimpleAudioEngine::pauseBackgroundMusic()
  6. {
  7. //在SimpleAudioEngineJni.cpp源文件中定义
  8. pauseBackgroundMusicJNI();
  9. }
  10. //--pauseBackgroundMusicJNI--->>>
  11. void pauseBackgroundMusicJNI()
  12. {
  13. // void pauseBackgroundMusic()
  14. JniMethodInfo methodInfo;
  15. if (! getStaticMethodInfo(methodInfo,"pauseBackgroundMusic","()V"))
  16. {
  17. return;
  18. }
  19. //通过jni调用java端的函数调用的是Cocos2dxHelper类中的
  20. /*
  21. public static void pauseBackgroundMusic() {
  22. Cocos2dxHelper.sCocos2dMusic.pauseBackgroundMusic();
  23. }
  24. */
  25. methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID);
  26. methodInfo.env->DeleteLocalRef(methodInfo.classID);
  27. }
  28.  
  29. 最终调用的是Cocos2dxMusic类中的
  30. public void pauseBackgroundMusic() {
  31. //mBackgroundMediaPlayer在(2)中有分析过,创建的MediaPlayer实例
  32. if (this.mBackgroundMediaPlayer != null && this.mBackgroundMediaPlayer.isPlaying()) {
  33. this.mBackgroundMediaPlayer.pause();
  34. this.mPaused = true; //是否暂停标志
  35. }
  36. }
  37.  
  38. 2
  39. 恢复播放
  40. void SimpleAudioEngine::resumeBackgroundMusic()
  41. {
  42. resumeBackgroundMusicJNI();
  43. }
  44. 其实和上面暂停的调用过程是一样的,就不分析了,直接进入java端看最终调用函数,public void resumeBackgroundMusic() {
  45. //这里只有处于暂停状态时即mPaused变量为true时,才会接着
  46. //上次播放的位置开始播放
  47. if (this.mBackgroundMediaPlayer != null && this.mPaused) {
  48. this.mBackgroundMediaPlayer.start();
  49. this.mPaused = false; //把暂停标志位设置false
  50. }
  51. }
  52.  
  53. 3
  54. 从头开始播放音乐文件
  55. void rewindBackgroundMusicJNI()
  56. {
  57. // void rewindBackgroundMusic()
  58. JniMethodInfo methodInfo;
  59. if (! getStaticMethodInfo(methodInfo,"rewindBackgroundMusic","()V"))
  60. {
  61. return;
  62. }
  63. methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID);
  64. methodInfo.env->DeleteLocalRef(methodInfo.classID);
  65. }
  66. --->>//java端函数
  67. //这个函数最终也是会调用playBackgroundMusic函数,但是和playBackgroundMusic有一点不同,
  68. //playBackgroundMusic需要传入音乐文件名,可以播放和上一次一样的音乐也可以和是上一次不一样的
  69. //音乐,但是rewindBackgroundMusic函数只有在mBackgroundMediaPlayer不为null时才执行,
  70. //也就是必须播放过音乐,且播放的是上次播放的音乐,只不过这次是从头开始播放
  71. public void rewindBackgroundMusic() {
  72. if (this.mBackgroundMediaPlayer != null) {
  73. playBackgroundMusic(mCurrentPath,mIsLoop);
  74. }
  75. }
  76.  
  77. 4、停止播放音乐文件
  78. void stopBackgroundMusicJNI()
  79. {
  80. // void stopBackgroundMusic()
  81. JniMethodInfo methodInfo;
  82. if (! getStaticMethodInfo(methodInfo,"stopBackgroundMusic",methodInfo.methodID);
  83. methodInfo.env->DeleteLocalRef(methodInfo.classID);
  84. }
  85.  
  86. --->>>//java端函数
  87. public void stopBackgroundMusic() {
  88. if (this.mBackgroundMediaPlayer != null) {
  89. mBackgroundMediaPlayer.release();
  90. //不太明白这里为什么有从新创建了MediaPlayer实例
  91. //可能是一些特殊情况下会出现问题?
  92. mBackgroundMediaPlayer = createMediaplayer(mCurrentPath);
  93.  
  94. // should set the state,if not,the following sequence will be error
  95. // play -> pause -> stop -> resume
  96. //为什么设置mPaused标志,直接看上面的英文注释
  97. this.mPaused = false;
  98. }
  99. }
  100. 5
  101. 返回是否处于播放状态
  102. bool isBackgroundMusicPlayingJNI()
  103. {
  104. // boolean rewindBackgroundMusic()
  105. JniMethodInfo methodInfo;
  106. jboolean ret = false;
  107. if (! getStaticMethodInfo(methodInfo,"isBackgroundMusicPlaying","()Z"))
  108. {
  109. return ret;
  110. }
  111. ret = methodInfo.env->CallStaticBooleanMethod(methodInfo.classID,methodInfo.methodID);
  112. methodInfo.env->DeleteLocalRef(methodInfo.classID);
  113. return ret;
  114. }
  115. --->>>//java端函数,没什么东西
  116. public boolean isBackgroundMusicPlaying() {
  117. boolean ret = false;
  118.  
  119. if (this.mBackgroundMediaPlayer == null) {
  120. ret = false;
  121. } else {
  122. ret = this.mBackgroundMediaPlayer.isPlaying();
  123. }
  124.  
  125. return ret;
  126. }
  127. 6获取播放声音音量值
  128. float getBackgroundMusicVolumeJNI()
  129. {
  130. // float getBackgroundMusicVolume()
  131. JniMethodInfo methodInfo;
  132. jfloat ret = -1.0;
  133. if (! getStaticMethodInfo(methodInfo,"getBackgroundMusicVolume","()F"))
  134. {
  135. return ret;
  136. }
  137. ret = methodInfo.env->CallStaticFloatMethod(methodInfo.classID,methodInfo.methodID);
  138. methodInfo.env->DeleteLocalRef(methodInfo.classID);
  139. return ret;
  140. }
  141. ------->>>//java
  142. public float getBackgroundVolume() {
  143. if (this.mBackgroundMediaPlayer != null) {
  144. return (this.mLeftVolume + this.mRightVolume) / 2;
  145. } else {
  146. return 0.0f;
  147. }
  148. }
  149.  
  150. 7、设置声音音量值
  151. void setBackgroundMusicVolumeJNI(float volume)
  152. {
  153. // void setBackgroundMusicVolume()
  154. JniMethodInfo methodInfo;
  155. if (! getStaticMethodInfo(methodInfo,"setBackgroundMusicVolume","(F)V"))
  156. {
  157. return ;
  158. }
  159. methodInfo.env->CallStaticVoidMethod(methodInfo.classID,volume);
  160. methodInfo.env->DeleteLocalRef(methodInfo.classID);
  161. }
  162. ---->>>java
  163. public void setBackgroundVolume(float pVolume) {
  164. if (pVolume < 0.0f) {
  165. pVolume = 0.0f;
  166. }
  167.  
  168. if (pVolume > 1.0f) {
  169. pVolume = 1.0f;
  170. }
  171.  
  172. this.mLeftVolume = this.mRightVolume = pVolume;
  173. if (this.mBackgroundMediaPlayer != null) {
  174. this.mBackgroundMediaPlayer.setVolume(this.mLeftVolume,this.mRightVolume);
  175. }
  176. }
  177.  
  178. 8
  179. end函数,这个一般在退出游戏是调用,关掉所有的音乐和音效。
  180. void endJNI()
  181. {
  182. // void end()
  183. JniMethodInfo methodInfo;
  184. if (! getStaticMethodInfo(methodInfo,"end","()V"))
  185. {
  186. return ;
  187. }
  188. methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID);
  189. methodInfo.env->DeleteLocalRef(methodInfo.classID);
  190. }
  191. --->>>//java端函数
  192. public static void end() {
  193. Cocos2dxHelper.sCocos2dMusic.end(); //背景音乐文件的处理
  194. Cocos2dxHelper.sCocos2dSound.end();
  195. }
  196.  
  197. ---->>>>/////背景音乐文件的处理
  198. public void end() {
  199. if (this.mBackgroundMediaPlayer != null) {
  200. this.mBackgroundMediaPlayer.release();
  201. }
  202. //把所有的变量恢复初始值
  203. /*
  204. private void initData() {
  205. this.mLeftVolume = 0.5f;
  206. this.mRightVolume = 0.5f;
  207. this.mBackgroundMediaPlayer = null;
  208. this.mPaused = false;
  209. this.mCurrentPath = null;
  210. }
  211. */
  212. this.initData();
  213. }

猜你在找的Cocos2d-x相关文章