转自:http://blog.csdn.net/honghaier/article/details/62494089
”房卡“麻将研发技巧,尽在”红孩儿的游戏开发之路“,欢迎关注公众号!
房卡麻将分析系列之"千里传音"
在房卡棋牌游戏中,因为要频繁的看牌,出牌。为了实时沟通打字聊天往往比较麻烦,通过语音交流,催牌可以很好的帮助玩家及时的表达情绪,增强游戏的气氛。
![](https://img-blog.csdn.net/20170316234053124)
那么这是怎么做到的呢?
首先这个过程分为三步:
一。录制声音并压缩成数据包:这个过程一般是当玩家点击按钮,开始录音,松开按钮,停止录音并生成WAV文件,之后通过编码转换压缩为
在这里要根据安卓和苹果两个平台来做区分。
- void startSoundRecord()
- {
- std::string kFileName = utility::toString(time(NULL),".wav");
- s_kRecordFileName = cocos2d::FileUtils::getInstance()->getWritablePath()+kFileName;
- #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
- JniMethodInfo minfo;
- bool isHave = JniHelper::getStaticMethodInfo(minfo,JAVA_CLASSNAME, "startSoundRecord", "(Ljava/lang/String;)V");
- if (isHave)
- {
- jstring jurl = minfo.env->NewStringUTF(kFileName.c_str());
- minfo.env->CallStaticVoidMethod(minfo.classID, minfo.methodID,jurl);
- cocos2d::log("JniFun call startSoundRecord over!");
-
- minfo.env->DeleteLocalRef(minfo.classID);
- }
- else
- {
- cocos2d::log("JniFun call startSoundRecord error!");
- }
- #endif
- #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
- IosHelper::beginRecord(s_kRecordFileName.c_str());
- #endif
- }
-
-
- const char* stopSoundRecord()
- {
- #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
- std::string str;
- JniMethodInfo minfo;
- bool isHave = JniHelper::getStaticMethodInfo(minfo,JAVA_CLASSNAME, "stopSoundRecord", "()Ljava/lang/String;");
- if (isHave)
- {
- jstring jFileName = (jstring)minfo.env->CallStaticObjectMethod(minfo.classID, minfo.methodID);
- const char *newStr = minfo.env->GetStringUTFChars(jFileName, 0);
- str = newStr;
- cocos2d::log("JniFun call stopSoundRecord over :");
- cocos2d::log("%s",str.c_str());
- minfo.env->ReleaseStringUTFChars(jFileName, newStr);
- minfo.env->DeleteLocalRef(minfo.classID);
- }
- else
- {
- cocos2d::log("JniFun call stopSoundRecord error!");
- }
- return str.c_str();
- #endif
- #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
- IosHelper::endRecord();
- return s_kRecordFileName.c_str();
- #endif
- return "";
- }
在Native.Java中实现录音和结束:
-
- public static void startSoundRecord( String SoundFileName)
- {
- String SoundFilePath= Environment.getExternalStorageDirectory().getAbsolutePath();
-
- if (filePath != null)
- {
- File file = new File(filePath);
- if (file!= null && file.exists())
- {
- file.delete();
- }
- }
- filePath = SoundFilePath+"/"+SoundFileName;
- recorder = new MediaRecorder();
-
- recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-
- recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
- recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
- recorder.setOutputFile(SoundFilePath+"/"+SoundFileName);
- try {
- recorder.prepare();
- recorder.start();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
-
- public static String stopSoundRecord()
- {
- recorder.stop();
- recorder.release();
- recorder = null;
- return filePath;
- }
另外,要在AndroidMainfest.xml中注意开启录音权限:
- <uses-permission android:name="android.permission.RECORD_AUDIO" />
iOS版本处理:需要在mm文件中完成相应函数
- AVAudioRecorder *recorder = NULL;
- void IosHelper::beginRecord(const charchar *_fileName)
- {
- if (recorder == nil)
- {
-
- NSString *recordFilePath = [NSString stringWithCString:_fileName encoding:NSUTF8StringEncoding];
-
- NSDictionary *recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
- [NSNumber numberWithFloat: 8000.0],AVSampleRateKey,
- [NSNumber numberWithInt: kAudioFormatLinearPCM],AVFormatIDKey,
- [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
- [NSNumber numberWithInt: 1], AVNumberOfChannelsKey,
- nil nil];
-
- NSError *error = nil;
- recorder = [[ AVAudioRecorder alloc] initWithURL:[NSURL URLWithString:recordFilePath] settings:recordSetting error:&error];
- }
- recorder.meteringEnabled = YES;
- [recorder prepareToRecord];
-
- UInt32 sessionCategory = kAudioSessionCategory_PlayAndRecord;
- AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
-
-
- UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
- AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride), &audioRouteOverride);
- [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord error:nil];
- [[AVAudioSession sharedInstance] setActive:YES error:nil];
- [recorder record];
- }
-
- const charchar * IosHelper::endRecord()
- {
- if (recorder == nil)
- return "";
- if (recorder.isRecording)
- [recorder stop];
- return "";
- }
![](https://img-blog.csdn.net/20170316234221234)
二。发送声音数据到服务器:在结束录制声音并生成文件后,将文件发送出去。
- std::string kFileName = JniFun::stopSoundRecord();
- sendTalkFile(m_pLocal->GetChairID(),kFileName);
这里就是将文件以数据包形式发送出去,不做详细表述。
三。接收数据并解压,播放: 在接收到消息后,将数据写入文件并播放即可。
- bool GameBase::RevTalk_File(CMD_GR_C_TableTalk* pNetInfo)
- {
- if (pNetInfo->strTalkSize == 0)
- {
- return true;
- }
- static int iIdex = 0;
- iIdex ++;
- std::string kFile = utility::toString(cocos2d::CCFileUtils::sharedFileUtils()->getWritablePath(),"TableTalk",iIdex,".arm");
- FILE *fp = fopen(kFile.c_str(), "wb");
-
- fseek(fp,0,SEEK_END);
- fseek(fp,0,SEEK_SET);
- fwrite(&pNetInfo->strTalkData,sizeof(unsigned char), pNetInfo->strTalkSize,fp);
- fclose(fp);
- int iAddTime = pNetInfo->strTalkSize/1200+2.0f;
- if (iAddTime > 10)
- {
- iAddTime = 10;
- }
- std::string kDestFile = kFile;
- utility::StringReplace(kDestFile,"arm","wav");
-
- ArmFun::ArmToWav(kFile.c_str(),kDestFile.c_str());
-
- SoundFun::Instance().PaseBackMusic();
- SoundFun::Instance().ResumeBackMusic(iAddTime);
- SoundFun::Instance().PaseEffectMusic();
- SoundFun::Instance().ResumeEffectMusic(iAddTime);
-
- SoundFun::Instance().playEffectDirect(kDestFile);
-
- GamePlayer* pPlayer = getBasePlayerByChairID(pNetInfo->cbChairID);
- if (pPlayer)
- {
- pPlayer->showTalkState(pNetInfo);
- }
-
- return true;
- }
最终,房卡棋牌中的语音聊天就完整的实现出来了,当然,这种方式并不完美,如果能开启P2P的实时语音对话就更好了。另外,这套代码中会不断的产生声音文件,这是个问题,小伙伴们可以在发送完声音和播放完声音后删除生成的声音文件,以免造成空间增长的BUG~
”房卡“麻将研发技巧,尽在”红孩儿的游戏开发之路“,欢迎关注公众号!
![](https://img-blog.csdn.net/20170316234434157)