我们上面几篇分析了cocos2dx音频模块的音乐部分,从这篇开始, 我们分析下音效部分: 1、 //预加载音效文件:pszFilePath 音效文件名 void SimpleAudioEngine::preloadEffect(const char* pszFilePath) { //获取音效文件的全路径,如果是apk包里的路径,则不包含assets/ std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath); preloadEffectJNI(fullPath.c_str()); } --->>>// 这里通过jni调用java端的方法 void preloadEffectJNI(const char *path) { // void preloadEffect(String) JniMethodInfo methodInfo; if (! getStaticMethodInfo(methodInfo,"preloadEffect","(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); } ----->>>//Cocos2dxHelper类中的方法: public static void preloadEffect(final String path) { //Cocos2dxSound类是专门处理音效的类 Cocos2dxHelper.sCocos2dSound.preloadEffect(path); } --->>>//Cocos2dxSound类的方法: public int preloadEffect(final String pPath) { //private final HashMap<String,Integer> mPathSoundIDMap = new HashMap<String,Integer>(); //这个是音效路径对应音效ID的map Integer soundID = this.mPathSoundIDMap.get(pPath); if (soundID == null) { // soundID = this.createSoundIDFromAsset(pPath); // save value just in case if file is really loaded // 如果createSoundIDFromAsset函数调用成功,则添加到mPathSoundIDMap中。 if (soundID != Cocos2dxSound.INVALID_SOUND_ID) { this.mPathSoundIDMap.put(pPath,soundID); } } return soundID; } ----->>>>根据我们传入的音效文件路径,加载音效 public int createSoundIDFromAsset(final String pPath) { int soundID = Cocos2dxSound.INVALID_SOUND_ID; try { //根据传入的路径不同,做不同处理,一个是绝对路径一个是包里的路径,加载音效文件 //The SoundPool class manages and plays audio resources for applications. //private SoundPool mSoundPool;音效缓存池 if (pPath.startsWith("/")) { soundID = this.mSoundPool.load(pPath,0); } else { soundID = this.mSoundPool.load(this.mContext.getAssets().openFd(pPath),0); } } catch (final Exception e) { soundID = Cocos2dxSound.INVALID_SOUND_ID; Log.e(Cocos2dxSound.TAG,"error: " + e.getMessage(),e); } // mSoundPool.load returns 0 if something goes wrong,for example a file does not exist if (soundID == 0) { soundID = Cocos2dxSound.INVALID_SOUND_ID; } return soundID; } 2、 播放音效文件。 pszFilePath:音效文件名;bLoop 是否循环播放 unsigned int SimpleAudioEngine::playEffect(const char* pszFilePath,bool bLoop) { std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath); return playEffectJNI(fullPath.c_str(),bLoop); } ----->>>playEffectJNI: unsigned int playEffectJNI(const char* path,bool bLoop) { // int playEffect(String) JniMethodInfo methodInfo; int ret = 0; if (! getStaticMethodInfo(methodInfo,"playEffect","(Ljava/lang/String;Z)I")) { return ret; } jstring stringArg = methodInfo.env->NewStringUTF(path); ret = methodInfo.env->CallStaticIntMethod(methodInfo.classID,stringArg,bLoop); methodInfo.env->DeleteLocalRef(stringArg); methodInfo.env->DeleteLocalRef(methodInfo.classID); return (unsigned int)ret; } ------>>>>playEffect: public int playEffect(final String pPath,final boolean pLoop) { //从mPathSoundIDMap中,根据音效path得到音效ID Integer soundID = this.mPathSoundIDMap.get(pPath); int streamID = Cocos2dxSound.INVALID_STREAM_ID; if (soundID != null) { // play sound // 如果音效ID存在,则表明我们已经预先加载过这个音效文件,则调用mSoundPool.play直接播放 /* private int doPlayEffect(final String pPath,final int soundId,final boolean pLoop) { // play sound // Play a sound from a sound ID. // return non-zero streamID if successful,zero if Failed如果成功会返回一个streamID int streamID = this.mSoundPool.play(soundId,this.mLeftVolume,this.mRightVolume,Cocos2dxSound.SOUND_PRIORITY,pLoop ? -1 : 0,Cocos2dxSound.SOUND_RATE); // record stream id // 记录上面调用mSoundPool.play返回的streamID,至于为什么需要这样,看下面源码的说明: // sound path and stream ids map // a file may be played many times at the same time // so there is an array map to a file path // private final HashMap<String,ArrayList<Integer>> mPathStreamIDsMap = new HashMap<String,ArrayList<Integer>>(); ArrayList<Integer> streamIDs = this.mPathStreamIDsMap.get(pPath); if (streamIDs == null) { streamIDs = new ArrayList<Integer>(); this.mPathStreamIDsMap.put(pPath,streamIDs); } streamIDs.add(streamID); return streamID; } */ streamID = this.doPlayEffect(pPath,soundID.intValue(),pLoop); } else { // the effect is not prepared,如果音效没有预先加载,则需要先加载 soundID = this.preloadEffect(pPath); //加载音效文件 if (soundID == Cocos2dxSound.INVALID_SOUND_ID) { // can not preload effect return Cocos2dxSound.INVALID_SOUND_ID; } // only allow one playEffect at a time,or the semaphore will not work correctly synchronized(this.mSoundPool) { // add this effect into mEffecToPlayWhenLoadedArray,and it will be played when loaded completely // 这个应该和mSoundPool加载音效文件有关,我也不是很明白 /* 不过在初始化时设置了一个:this.mSoundPool.setOnLoadCompleteListener(new OnLoadCompletedListener()); //加载完成回调函数,应该和这个有关,这里我们就不关心了。 只需要知道如果我们提前加载音效文件 //也可以直接调用play函数,在调用play函数时会调用加载函数,并放入加载队列中,加载完成后进行播放。 //这样做会有一些延时,所以我们还是最好先加载,然后在播放。这个延时只在第一次播放时有,以后就不会了, //不过最好还是先加载。 */ /* @Override //加载完成回调函数。 public void onLoadComplete(SoundPool soundPool,int sampleId,int status) { if (status == 0) { // only play effect that are in mEffecToPlayWhenLoadedArray for ( SoundInfoForLoadedCompleted info : mEffecToPlayWhenLoadedArray) { if (sampleId == info.soundID) { // set the stream id which will be returned by playEffect() // 加载完成后调用doPlayEffect进行播放,并从mEffecToPlayWhenLoadedArray加载列表中 // 移除。 mStreamIdSyn = doPlayEffect(info.path,info.soundID,info.isLoop); // remove it from array,because we will break here // so it is safe to do mEffecToPlayWhenLoadedArray.remove(info); break; } } } else { mStreamIdSyn = Cocos2dxSound.INVALID_SOUND_ID; } mSemaphore.release(); } } */ mEffecToPlayWhenLoadedArray.add(new SoundInfoForLoadedCompleted(pPath,pLoop)); try { // wait OnloadedCompleteListener to set streamID this.mSemaphore.acquire(); streamID = this.mStreamIdSyn; } catch(Exception e) { return Cocos2dxSound.INVALID_SOUND_ID; } } } return streamID; }
我们上一篇中分析的音效部分的预加载和播放函数,这一篇来分析下其他函数: 1、 暂停某个播放中的音效 //注意这里的nSoundId不是java端的soundID,而是streamID //不要被参数的名字迷惑了。 void SimpleAudioEngine::pauseEffect(unsigned int nSoundId) { pauseEffectJNI(nSoundId); } -->> void pauseEffectJNI(unsigned int nSoundId) { // void pauseEffect(int) JniMethodInfo methodInfo; if (! getStaticMethodInfo(methodInfo,"pauseEffect","(I)V")) { return ; } methodInfo.env->CallStaticVoidMethod(methodInfo.classID,(int)nSoundId); methodInfo.env->DeleteLocalRef(methodInfo.classID); } ---->>//java端方法: //pStreamID 这个参数,是调用unsigned int SimpleAudioEngine::playEffect()放回的StreamID public void pauseEffect(final int pStreamID) { /** * Pause a playback stream. 暂停一个播放流 * * Pause the stream specified by the streamID. This is the * value returned by the play() function. (这个值是play函数的返回值)If the stream is * playing,it will be paused. If the stream is not playing * (e.g. is stopped or was prevIoUsly paused),calling this * function will have no effect.(如果处于播放状态则暂停,如果不处于播放状态,则无效) * * @param streamID a streamID returned by the play() function */ this.mSoundPool.pause(pStreamID); } 2、 暂停所有音效 void pauseAllEffectsJNI() { // void pauseAllEffects() JniMethodInfo methodInfo; if (! getStaticMethodInfo(methodInfo,"pauseAllEffects","()V")) { return ; } methodInfo.env->CallStaticVoidMethod(methodInfo.classID,methodInfo.methodID); methodInfo.env->DeleteLocalRef(methodInfo.classID); } --->>java端 public void pauseAllEffects() { /** * Pause all active streams. //暂停所有正在播放的音效 * * Pause all streams that are currently playing. This function * iterates through all the active streams and pauses any that * are playing. It also sets a flag so that any streams that * are playing can be resumed by calling autoResume(). */ this.mSoundPool.autoPause(); } 3、 恢复播放某个音效 void resumeEffectJNI(unsigned int nSoundId) { // void resumeEffect(int) JniMethodInfo methodInfo; if (! getStaticMethodInfo(methodInfo,"resumeEffect",(int)nSoundId); methodInfo.env->DeleteLocalRef(methodInfo.classID); } --->> public void resumeEffect(final int pStreamID) { /** * Resume a playback stream. * 只有处于暂停状态的stream,才可以恢复播放。 * Resume the stream specified by the streamID. This * is the value returned by the play() function. If the stream * is paused,this will resume playback. If the stream was not * prevIoUsly paused,calling this function will have no effect. * * @param streamID a streamID returned by the play() function */ this.mSoundPool.resume(pStreamID); } 4、 void resumeAllEffectsJNI() { // void resumeAllEffects() JniMethodInfo methodInfo; if (! getStaticMethodInfo(methodInfo,"resumeAllEffects",methodInfo.methodID); methodInfo.env->DeleteLocalRef(methodInfo.classID); } ---->>>java端函数: public void resumeAllEffects() { // can not only invoke SoundPool.autoResume() here,because // it only resumes all effects paused by pauseAllEffects() // 这里注释的很清楚 if (!this.mPathStreamIDsMap.isEmpty()) { final Iterator<Entry<String,ArrayList<Integer>>> iter = this.mPathStreamIDsMap.entrySet().iterator(); while (iter.hasNext()) { final Entry<String,ArrayList<Integer>> entry = iter.next(); for (final int pStreamID : entry.getValue()) { this.mSoundPool.resume(pStreamID); } } } } 5、 停止播放某个音效,这里的nSoundId同样是pStreamID,停止播放的音效是不能 通过resume恢复播放的。 void SimpleAudioEngine::stopEffect(unsigned int nSoundId) { stopEffectJNI(nSoundId); } --->>java端: public void stopEffect(final int pStreamID) { /** * Stop a playback stream. * * Stop the stream specified by the streamID. This * is the value returned by the play() function. If the stream * is playing,it will be stopped. It also releases any native * resources associated with this stream. (会释放对应的资源)If the stream is not * playing,it will have no effect. * * @param streamID a streamID returned by the play() function */ this.mSoundPool.stop(pStreamID); // remove record // 从记录列表中移除 for (final String pPath : this.mPathStreamIDsMap.keySet()) { if (this.mPathStreamIDsMap.get(pPath).contains(pStreamID)) { this.mPathStreamIDsMap.get(pPath).remove(this.mPathStreamIDsMap.get(pPath).indexOf(pStreamID)); break; } } } 6、 void stopAllEffectsJNI() { // void stopAllEffects() JniMethodInfo methodInfo; if (! getStaticMethodInfo(methodInfo,"stopAllEffects",methodInfo.methodID); methodInfo.env->DeleteLocalRef(methodInfo.classID); } --->>>java端 @SuppressWarnings("unchecked") public void stopAllEffects() { // stop effects,停止所有音效的播放 if (!this.mPathStreamIDsMap.isEmpty()) { final Iterator<?> iter = this.mPathStreamIDsMap.entrySet().iterator(); while (iter.hasNext()) { final Map.Entry<String,ArrayList<Integer>> entry = (Map.Entry<String,ArrayList<Integer>>) iter.next(); for (final int pStreamID : entry.getValue()) { this.mSoundPool.stop(pStreamID); } } } // remove records,清空播放记录 this.mPathStreamIDsMap.clear(); } 7、 卸载加载的音效(重要) //pszFilePath:音效文件名 void SimpleAudioEngine::unloadEffect(const char* pszFilePath) { std::string fullPath = getFullPathWithoutAssetsPrefix(pszFilePath); unloadEffectJNI(fullPath.c_str()); } --->> void unloadEffectJNI(const char* path) { // void unloadEffect(String) JniMethodInfo methodInfo; if (! getStaticMethodInfo(methodInfo,"unloadEffect",stringArg); methodInfo.env->DeleteLocalRef(stringArg); methodInfo.env->DeleteLocalRef(methodInfo.classID); } --->>> public void unloadEffect(final String pPath) { // stop effects // 先停止 final ArrayList<Integer> streamIDs = this.mPathStreamIDsMap.get(pPath); if (streamIDs != null) { for (final Integer pStreamID : streamIDs) { this.mSoundPool.stop(pStreamID); } } this.mPathStreamIDsMap.remove(pPath); // unload effect final Integer soundID = this.mPathSoundIDMap.get(pPath); if(soundID != null){ /** * Unload a sound from a sound ID. * * Unloads the sound specified by the soundID. This is the value * returned by the load() function. Returns true if the sound is * successfully unloaded,false if the sound was already unloaded. * * @param soundID a soundID returned by the load() function * @return true if just unloaded,false if prevIoUsly unloaded */ // 卸载音效 this.mSoundPool.unload(soundID); // 从mPathSoundIDMap中移除 this.mPathSoundIDMap.remove(pPath); } } /************************************************ 总结: 音效部分最重要的函数是preloadEffect,unloadEffect,playEffect,stopAllEffects 其他的感觉不是很常用,java有两个比较重要的map,一个是: private final HashMap<String,Integer>(); 这个是用来存放所有加载的音效文件路径和SoundID的map。 一个是: // sound path and stream ids map cocos2dx原本注释 // a file may be played many times at the same time // so there is an array map to a file path private final HashMap<String,ArrayList<Integer>>(); 用来存放所有播放的音效文件路径和StreamIDs的map。 注意:这里的SoundID和StreamID不是一个概念,一个音效文件只对应一个SoundID,而却可以对应多个StreamID,因为一个 音效文件可以播放多次(StreamID),但是只需要加载一次(SoundID). ************************************************/