房卡麻将分析系列之"千里传音"

 ”房卡“麻将研发技巧,尽在”红孩儿的游戏开发之路“,欢迎关注公众号!



               房卡麻将分析系列之"千里传音"


                   在房卡棋牌游戏中,因为要频繁的看牌,出牌。为了实时沟通打字聊天往往比较麻烦,通过语音交流,催牌可以很好的帮助玩家及时的表达情绪,增强游戏的气氛。

                                       



           那么这是怎么做到的呢?

           

           首先这个过程分为三步: 

           

           一。录制声音并压缩成数据包:这个过程一般是当玩家点击按钮,开始录音,松开按钮,停止录音并生成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);  
                //设置编码格式为AMR
		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 char *_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,//采样位数 默认 16
                                       [NSNumber numberWithInt: 1], AVNumberOfChannelsKey,//通道的数目
                                       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 char * IosHelper::endRecord()
{
        if (recorder == nil)
        return "";
        if (recorder.isRecording)
        [recorder stop];
	return "";
}


                                           
            


          二。发送声音数据到服务器:在结束录制声音并生成文件后,将文件发送出去。


	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");
        //这里需要做一个解压转换,将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~


 ”房卡“麻将研发技巧,尽在”红孩儿的游戏开发之路“,欢迎关注公众号!

                             


原创文章 197 获赞 526 访问量 140万+

猜你喜欢

转载自blog.csdn.net/honghaier/article/details/62494089