RakNet在Win平台上已经实现消息、语音、文件传输了,但在Android平台下尚未实现,笔者决定把源码移植到Android平台下测试。
项目自带Chat Example Client和Chat Example Server实现消息,源码简单易懂,此处就不介绍了,直接贴上图片。
测试通过,消息是以Toast方式显示的,图片未捕捉到显示。
Win平台下实现语音是通过Portaudio进行的,Portaudio尚未支持Android,要实现语音怎么办?
Java层实现可以,但笔者觉得麻烦,决定移植Portaudio到Android。关于这方面的信息请查看博客:Android RakNet 系列之三 移植Portaudio。
实现原理:Raknet通过PortAudio进行语音采集以及播放,再通过RakVoice语音插件进行语音发送与接受,RakVoice中使用了speex编码传输。
Jni实现主要代码如下:
class Voice { //声音一个操作语音的类 public: Voice(); virtual ~Voice(); void run(const char* ip); //关键执行 int portInAudioCallback(const void *inputBuffer); int portOutAudioCallback(void *inputBuffer); void waitForStop(); void stop(); public: bool mute; RakNet::RakPeerInterface *rakPeer; RakNet::RakVoice rakVoice; PaError err; bool isStoped; bool runState; };
void Voice::run(const char* ip) { //通过Ip连接对方机子,然后传输。 //////////////////////////////////////////// if (err != paNoError) { LOGI("Pa_Initialize fail: %s",Pa_GetErrorText(err)); return; } mute = false; PaStream *stream; unsigned int maxConnectionsAllowed = 4; unsigned int maxPlayersPerServer = 4; unsigned short serverPort = 6000; RakNet::SocketDescriptor socketDescriptor(serverPort, 0); if (rakPeer->Startup(maxConnectionsAllowed, &socketDescriptor, 1) != RakNet::RAKNET_STARTED) { LOGI("Startup fail:"); return; } rakPeer->SetMaximumIncomingConnections(maxPlayersPerServer); rakPeer->AttachPlugin(&rakVoice); rakVoice.Init(SAMPLE_RATE, FRAMES_PER_BUFFER * sizeof (short)); PaDeviceIndex numdev; const PaDeviceInfo *info; int i; numdev = Pa_GetDeviceCount(); PaStreamParameters inparam, outparam; memset(&inparam, 0, sizeof (PaStreamParameters)); inparam.device = Pa_GetDefaultInputDevice(); inparam.channelCount = 1; inparam.sampleFormat = paInt16; memset(&outparam, 0, sizeof (PaStreamParameters)); outparam.device = Pa_GetDefaultOutputDevice(); outparam.channelCount = 1; outparam.sampleFormat = paInt16; PaError err = Pa_OpenStream(&stream, &inparam, &outparam, SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff, PAInOutCallback, this); err = Pa_SetStreamFinishedCallback(stream, &StreamFinished ); if(err != paNoError){ LOGI("Cannot set stream finish callback"); return; } if (err != paNoError) { LOGI("Pa_OpenStream fail: %s",Pa_GetErrorText(err)); return; } err = Pa_StartStream(stream); if (err != paNoError) { LOGI("Pa_StartStream fail: %s",Pa_GetErrorText(err)); return; } if (ip) { LOGI("Connect: %s",ip); rakPeer->Connect(ip, serverPort, 0, 0); } RakNet::Packet *p; unsigned char typeId; isStoped = false; runState = true; while (1) { if(!runState) { Pa_AbortStream(stream); Pa_CloseStream(stream); isStoped = true; return; } p=rakPeer->Receive(); while (p) { LOGI("Receive data from: %s guid",p->systemAddress.ToString(),p->guid.ToString()); if (p->data[0]==ID_CONNECTION_REQUEST_ACCEPTED) { LOGI("ID_CONNECTION_REQUEST_ACCEPTED from %s\n", p->systemAddress.ToString()); rakVoice.RequestVoiceChannel(p->guid); } else if (p->data[0]==ID_CONNECTION_ATTEMPT_FAILED) { LOGI("ID_CONNECTION_ATTEMPT_FAILED\n"); } else if (p->data[0]==ID_RAKVOICE_OPEN_CHANNEL_REQUEST || p->data[0]==ID_RAKVOICE_OPEN_CHANNEL_REPLY) { LOGI("Got new channel from %s\n", p->systemAddress.ToString()); } else if (p->data[0]==ID_NAT_TARGET_NOT_CONNECTED) { RakNet::RakNetGUID g; RakNet::BitStream b(p->data, p->length, false); b.IgnoreBits(8); // Ignore the ID_... b.Read(g); LOGI("ID_NAT_TARGET_NOT_CONNECTED for %s\n", g.ToString()); } else if (p->data[0]==ID_NAT_TARGET_UNRESPONSIVE) { RakNet::RakNetGUID g; RakNet::BitStream b(p->data, p->length, false); b.IgnoreBits(8); // Ignore the ID_... b.Read(g); LOGI("ID_NAT_TARGET_UNRESPONSIVE for %s\n", g.ToString()); } else if (p->data[0]==ID_NAT_CONNECTION_TO_TARGET_LOST) { RakNet::RakNetGUID g; RakNet::BitStream b(p->data, p->length, false); b.IgnoreBits(8); // Ignore the ID_... b.Read(g); LOGI("ID_NAT_CONNECTION_TO_TARGET_LOST for %s\n", g.ToString()); } else if (p->data[0]==ID_NAT_ALREADY_IN_PROGRESS) { RakNet::RakNetGUID g; RakNet::BitStream b(p->data, p->length, false); b.IgnoreBits(8); // Ignore the ID_... b.Read(g); LOGI("ID_NAT_ALREADY_IN_PROGRESS for %s\n", g.ToString()); } else if (p->data[0]==ID_NAT_PUNCHTHROUGH_FAILED) { LOGI("ID_NAT_PUNCHTHROUGH_FAILED for %s\n", p->guid.ToString()); } else if (p->data[0]==ID_NAT_PUNCHTHROUGH_SUCCEEDED) { LOGI("ID_NAT_PUNCHTHROUGH_SUCCEEDED for %s. Connecting...\n", p->guid.ToString()); rakPeer->Connect(p->systemAddress.ToString(false),p->systemAddress.GetPort(),0,0); } else if (p->data[0]==ID_ALREADY_CONNECTED) { LOGI("ID_ALREADY_CONNECTED\n"); } else if (p->data[0]==ID_RAKVOICE_CLOSE_CHANNEL) { LOGI("ID_RAKVOICE_CLOSE_CHANNEL\n"); } else if (p->data[0]==ID_DISCONNECTION_NOTIFICATION) { LOGI("ID_DISCONNECTION_NOTIFICATION\n"); } else if (p->data[0]==ID_NEW_INCOMING_CONNECTION) { LOGI("ID_NEW_INCOMING_CONNECTION\n"); } else if(p->data[0]==ID_CONNECTION_LOST) { LOGI("ID_CONNECTION_LOST 可靠的数据包不能被传递到指定的分组系统"); } else { LOGI("Unknown packet ID %i\n", p->data[0]); } rakPeer->DeallocatePacket(p); p=rakPeer->Receive(); } Pa_Sleep( 30 ); } }
效果如图:
class CBTransferInterface : public RakNet::FileListTransferCBInterface //传输文件接口 { public: bool OnFile(OnFileStruct *onFileStruct); virtual void OnFileProgress(FileProgressStruct *fps); virtual bool OnDownloadComplete(DownloadCompleteStruct *dcs); }; // Sender progress notification class TestFileListProgress : public RakNet::FileListProgress //进度通知 { virtual void OnFilePush(const char *fileName, unsigned int fileLengthBytes, unsigned int offset, unsigned int bytesBeingSent, bool done, RakNet::SystemAddress targetSystem, unsigned short setID); virtual void OnFilePushesComplete( RakNet::SystemAddress systemAddress, unsigned short setID ); virtual void OnSendAborted( RakNet::SystemAddress systemAddress ); }; class FileTransferClient //客户端 { public: int testTransfer(const char* ip,const char* filePath); void stopTransfer(); void waitForStop(); public: int transferStatus; int isStoped; };