我们上面几篇分析了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, methodInfo.methodID, 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, soundID.intValue(), 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, methodInfo.methodID, (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", "(I)V"))
{
return ;
}
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, (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", "()V"))
{
return ;
}
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, 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", "()V"))
{
return ;
}
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, 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", "(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);
}
--->>>
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> mPathSoundIDMap = new 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>> mPathStreamIDsMap = new HashMap<String, ArrayList<Integer>>();
用来存放所有播放的音效文件路径和StreamIDs的map。
注意:这里的SoundID和StreamID不是一个概念,一个音效文件只对应一个SoundID,而却可以对应多个StreamID,因为一个
音效文件可以播放多次(StreamID),但是只需要加载一次(SoundID).
************************************************/