上一节 解决了读文件的问题,游戏也跑起来了,可是音效和背景音乐,死活播放不出来。回想一下,的确没有考虑到游戏的音效问题。让我们再折腾一下,播放zip里的音效问题。cocos-x 安卓的背景音乐处理交给了Cocos2dxMusic.java,音效处理交给了Cocos2dxSound.java。别的我们不看,就关注声音资源加载的地方。
先分析一下Cocos2dxMusic.java 的声音资源加载方法
private MediaPlayer createMediaplayer(final String pPath) ;
这个方法就是啦。大概就是判断fullpath路径是否 '/'开头,如果是就加载sd卡声音资源,否则加载apk里的资源。
/** * create mediaplayer for music * * @param pPath * the pPath relative to assets * @return */ private MediaPlayer createMediaplayer(final String pPath) { MediaPlayer mediaPlayer = new MediaPlayer(); try { //加载sd卡里的声音资源 if (pPath.startsWith("/")) { final FileInputStream fis = new FileInputStream(pPath); mediaPlayer.setDataSource(fis.getFD()); fis.close(); } else { //加载apk里的声音资源 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; }
在分析一下Cocos2dxSound.java的声音资源加载方法
->public int playEffect(final String pPath,final boolean pLoop)
->public int preloadEffect(final String pPath)
->public int createSoundIDFromAsset(final String pPath)
也是是判断fullpath路径是否 '/'开头,如果是就加载sd卡声音资源,否则加载apk里的资源。
public int createSoundIDFromAsset(final String pPath) { int soundID = Cocos2dxSound.INVALID_SOUND_ID; try { if (pPath.startsWith("/")) { //加载sd卡声音资源 soundID = this.mSoundPool.load(pPath,0); } else { //加载apk里的声音资源 soundID = this.mSoundPool.load(this.mContext.getAssets().openFd(pPath),0); } } catch (final Exception e) { soundID = Cocos2dxSound.INVALID_SOUND_ID; Log.e(Cocos2dxSound.TAG,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; }
回想起来,我们已经有fullpath ,我们要像读取向下面的资源
fullpath = /storage/emulated/0/DonutABC/unitRes/game_22.zip#/res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav
方法1:直接读取zip里的声音流 让播放器们播放
InputStream 这样的东西java 程序猿们太熟悉不过了,何况是ZipInputStream。我们先将对应的文件用ZipInputStream 读出,然后直接让MediaPlayer 或SoundPool 加载播放流不就可以了吗?可是童话都是骗人的。突然想起邓超演的《美人鱼》,我实在是想不通。好好的白富美在身边不要,偏偏去要一条上半身是人,下半身是鱼的人鱼。没有接口下半辈子能幸福吗!!!实在是想不通。在这个问题上,也是遇到了这样的人鱼。MediaPlayer 和 SoundPool 都没有直接播放流的方法 ,MediaPlayer的setDataSource方法。SoundPool的load方法 都没有播放流的接口。下半辈子不幸福,感觉不会再爱了。好吧换第二个方法
方法2:将zip对应的声音文件临时解压到sd卡里, 然后返回fullpath给Cocos2dxMusic.java或Cocos2dxSound.java 的加载方法。
新欢没有接口,我们找旧爱。旧爱的接口就是要个声音文件的fullpath吗,太容易满足了。让我们动手改改
Cocos2dxMusic.java 的 createMediaplayer
/** * 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("/")) {//改了这里 String ppPath = PathUtils.getZipFilePath(pPath); final FileInputStream fis = new FileInputStream(ppPath); mediaPlayer.setDataSource(fis.getFD()); fis.close(); } else { final AssetFileDescriptor assetFileDescritor = this.mContext.getAssets().openFd(pPath); mediaPlayer.setDataSource(assetFileDescritor.getFileDescriptor(),e); } return mediaPlayer; }
Cocos2dxSound.java 的createSoundIDFromAsset
public int createSoundIDFromAsset(final String pPath) { int soundID = Cocos2dxSound.INVALID_SOUND_ID; try { if (pPath.startsWith("/")) {//改了下面的 String ppPath = PathUtils.getZipFilePath(pPath); soundID = this.mSoundPool.load(ppPath,0); } else { soundID = this.mSoundPool.load(this.mContext.getAssets().openFd(pPath),for example a file does not exist if (soundID == 0) { soundID = Cocos2dxSound.INVALID_SOUND_ID; } return soundID; }
解压的方法getZipFilePath
/*** * 获得路径 也许是zip里面的的路径 * @param pPath * @return * @throws ZipException * @throws IOException */ public static String getZipFilePath(String pPath) throws ZipException,IOException { String zipfilepath = ""; String filename = ""; int index = pPath.indexOf("#"); String ppPath = pPath; //是否在zip里 if(index != -1){ zipfilepath = pPath.substring(0,index); filename = pPath.substring(index+2); Log.d("RecordManager","zipfilepath:"+ zipfilepath + "--filename:" + filename); String filesavename = filename.replaceAll("/","_"); ppPath = PathUtils.getTempPath() +filesavename+".temp"; File filetemp = new File(ppPath); //是否有临时解压文件 避免重复解压 if(!filetemp.exists()){ filetemp.createNewFile(); ZipFile file = new ZipFile(zipfilepath); FileHeader fileHeader = file.getFileHeader(filename); net.lingala.zip4j.io.ZipInputStream zipInputStream = file.getInputStream(fileHeader); FileOutputStream fo = new FileOutputStream(ppPath); byte[] b = new byte[4096]; int readLine = -1; while ((readLine = zipInputStream.read(b)) != -1) { fo.write(b,readLine); } fo.close(); zipInputStream.close(); } } return ppPath; }
注:我用了开源的zip操作库 zip4j ,我git上有提交 。
好啦。我们这就跑起来。欧啦!声音出来了。这个方案凑合着用,如有更高明的方法,请回复我。
解压方法 c++版
static std::string getZipFilePath(const std::string& fullPath,const std::string& saveDir); static unsigned char* getFileDataFromZip(const char* pszZipFilePath,const char* pszFileName,unsigned long * pSize);
#include "support/zip_support/ZipUtils.h" #include "platform/CCCommon.h" #include "support/zip_support/unzip.h" #include "platform/CCFileUtils.h"
/** * fullpath 音效的绝对路径 * saveDir 解压到的地方 * return 解压后的资源返回的路径 **/ std::string JNItools::getZipFilePath(const std::string& fullPath,const std::string& saveDir){ unsigned char * pBuffer = NULL; unsigned long pSize = 0; std::string savepath = fullPath; std::string pszFileNameTemp = ""; std::string pszZipFilePath = "";
size_t pos = fullPath.find_last_of("#"); if (pos != std::string::npos) { // file_path = /storage/emulated/0/DonutABC/unitRes/game_22.zip pszZipFilePath = fullPath.substr(0,pos); // file = res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav pszFileNameTemp = fullPath.substr(pos+2); // CCLOG("pszZipFilePath:%s,pszFileNameTemp:%s",pszZipFilePath.c_str(),pszFileNameTemp.c_str()); //替换‘/' std::string filename = pszFileNameTemp; std::string old_value = "/"; std::string new_value = "_"; for(string::size_type pos(0); pos!=string::npos; pos+=new_value.length()){ if((pos=filename.find(old_value,pos))!=string::npos) filename.replace(pos,old_value.length(),new_value); else break; } // CCLOG("filename:%s",filename.c_str()); savepath = saveDir + filename + ".temp"; const char* output = savepath.c_str(); CCLOG("filename:%s",output); //文件如果存在就不解压了 int i = access(savepath.c_str(),0); CCLOG("filename:%d",i); if( i == -1){ pBuffer = JNItools::getFileDataFromZip(pszZipFilePath.c_str(),pszFileNameTemp.c_str(),&pSize); FILE *savefile = fopen(output,"wb"); fwrite(pBuffer,1,(size_t)pSize,savefile); fflush(savefile); fclose(savefile); delete pBuffer; } } return savepath; } /** * 解压zip 获得资源数据 **/ unsigned char* JNItools::getFileDataFromZip(const char* pszZipFilePath,unsigned long * pSize) { unsigned char * pBuffer = NULL; unzFile pFile = NULL; *pSize = 0; do { // CCLOG("1"); CC_BREAK_IF(!pszZipFilePath || !pszFileName); // CCLOG("11"); CC_BREAK_IF(strlen(pszZipFilePath) == 0); // CCLOG("1111"); pFile = unzOpen(pszZipFilePath); CC_BREAK_IF(!pFile); // CCLOG("2"); int nRet = unzLocateFile(pFile,pszFileName,1); CC_BREAK_IF(UNZ_OK != nRet); // CCLOG("3"); char szFilePathA[260]; unz_file_info FileInfo; nRet = unzGetCurrentFileInfo(pFile,&FileInfo,szFilePathA,sizeof(szFilePathA),NULL,0); CC_BREAK_IF(UNZ_OK != nRet); // CCLOG("4"); nRet = unzOpenCurrentFile(pFile); CC_BREAK_IF(UNZ_OK != nRet); // CCLOG("5"); pBuffer = new unsigned char[FileInfo.uncompressed_size]; int CC_UNUSED nSize = unzReadCurrentFile(pFile,pBuffer,FileInfo.uncompressed_size); CCAssert(nSize == 0 || nSize == (int)FileInfo.uncompressed_size,"the file size is wrong"); // CCLOG("6"); *pSize = FileInfo.uncompressed_size; unzCloseCurrentFile(pFile); } while (0); if (pFile) { unzClose(pFile); } return pBuffer; }
调方法
public static native String getZipFilePath(String pPath,String saveDir);
/** * jni 调用 */ jstring Java_org_cocos2dx_lib_PathUtils_getZipFilePath(JNIEnv* env,jobject thiz,jstring path,jstring savedir) { // CCLOG("filename:%s","Java_org_cocos2dx_lib_PathUtils_getZipFilePath"); std::string char_path = JniHelper::jstring2string(path); std::string char_savedir = JniHelper::jstring2string(savedir); std::string str_path = JNItools::getZipFilePath(char_path,char_savedir); // CCLOG("----getPath:%s",str_path.c_str()); env->DeleteLocalRef(path); env->DeleteLocalRef(savedir); return (env)->NewStringUTF(str_path.c_str()); }
public int createSoundIDFromAsset(final String pPath) { int soundID = Cocos2dxSound.INVALID_SOUND_ID; try { if (pPath.startsWith("/")) { String ppPath = PathUtils.getZipFilePath(pPath,PathUtils.getTempPath()); soundID = this.mSoundPool.load(ppPath,for example a file does not exist if (soundID == 0) { soundID = Cocos2dxSound.INVALID_SOUND_ID; } return soundID; }
Cocos2dxMusic.java 修改
/** * 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("/")) { String ppPath = PathUtils.getZipFilePath(pPath,PathUtils.getTempPath()); final FileInputStream fis = new FileInputStream(ppPath); mediaPlayer.setDataSource(fis.getFD()); fis.close(); } else { final AssetFileDescriptor assetFileDescritor = this.mContext.getAssets().openFd(pPath); mediaPlayer.setDataSource(assetFileDescritor.getFileDescriptor(),e); } return mediaPlayer; }
整体来看效率没什么提高,就是不用zip4j 解压了。
还是老样子。下载完整的改动
本文出处