cocos2dx 音频模块分析(2): 音效部分

前端之家收集整理的这篇文章主要介绍了cocos2dx 音频模块分析(2): 音效部分前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我们上面几篇分析了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).


************************************************/
原文链接:https://www.f2er.com/cocos2dx/340394.html

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