简介
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 );
}
}
效果如图:
实现文件传输
Win平台下的文件传输Demo直接移植到Android平台便可实现,代码如下: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;
};
效果如图: