PeerCast分析要点一(PeerCast和媒体播放器的通信原理)

来源:互联网 发布:js 二维数组赋值 编辑:程序博客网 时间:2024/05/23 01:18

PeerCast和媒体播放器的通信原理

这里分析的仅以MP3为例,PeerCast和播放器的数据通信是通过HTTP协议来实现
首先播放器通过向本地PeerCast发送http请求(注意媒体数据是从PeerCast本地的缓冲区内读取到播放器的,通过以下的请求地址实现)通过向PeerCast的主服务线程7144端口发送HTTP请求
http://localhost:7144/stream/4DA260ACBCD97D2251E59CEC3A3F73D3.mp3

播放器发送的请求类似:
GET /stream/4DA260ACBCD97D2251E59CEC3A3F73D3.mp3 HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: foFileURI.dlna.org
Accept: */*
User-Agent: NSPlayer/12 .00 .7601 .17514 WMFSDK/12.00 .7601 .17514
Icy-Metadata: 1
Accept-Encoding: gzip,deflate
Host: localhost:7144

 

紧接着PeerCast开启了7144的线程一直做监听工作,当接收到以上的HTTP请求时
PeerCast调用handshakeHTTP来处理该请求
void Servent::handshakeHTTP(HTTP &http, bool isHTTP)
{
 char *in = http.cmdLine;
 
 if (http.isRequest("GET /"))
 {
  char *fn = in+4;
     if (strncmp(fn,"/stream/",8)==0)
   triggerChannel(fn+8,ChanInfo::SP_HTTP,isPrivate());
 }
}

 

过后便通过triggerChannel来触发频道,调用processStream传输媒体数据给播放器
void Servent::triggerChannel(char *str, ChanInfo::PROTOCOL proto,bool relay)
{
 outputProtocol = proto;
 processStream(false,info);
}

 

在processStream函数中,首先通过handshakeStream对播放器的Http请求进行处理


void Servent::processStream(bool doneHandshake,ChanInfo &chanInfo)

if (!doneHandshake)
{
 setStatus(S_HANDSHAKE);
 if (!handshakeStream(chanInfo))
  return;
}
if (outputProtocol == ChanInfo::SP_HTTP)

if ((addMetadata) && (chanMgr->icyMetaInterval))
 sendRawMetaChannel(chanMgr->icyMetaInterval);
else
 sendRawChannel(true,true);
}

 

在handshakeStream函数中,从http的请求中读取相关数据,如若频道源还未找到,则在该方法中继续寻找频道源,同时发送以下的http回复给播放器,过后就是真正的数据传送了


回复播放器的请求
HTTP/1.1 200 OK
Server: nginx
Date: Mon, 22 Aug 2011 09:13:30 GMT
Content-Type: audio/mpeg
Content-Length: 4073817
Last-Modified: Fri, 08 Apr 2011 09:22:53 GMT
Connection: keep-Alive
Accept-Ranges: bytes

ID3---(表示该音乐的相关歌曲信息)

 

// outputProtocol为HTTP协议,调用sendRawChannel发送数据
void Servent::processStream(bool doneHandshake,ChanInfo &chanInfo)
{
 if (outputProtocol == ChanInfo::SP_HTTP)
 {
  sendRawChannel(true,true);
 }

 

用sendRawChannel来发送频道数据给播放器(PS:这一块详细分析如何用http来传送流媒体数据的过程)


void Servent::sendRawChannel(bool sendHead, bool sendData)
{
 try
 {
 //设置direct直连方式的超时时间

  sock->setWriteTimeout(DIRECT_WRITE_TIMEOUT*1000);
 //在此对频道进行查找,确定频道是否已经被cut掉了
  Channel *ch = chanMgr->findChannelByID(chanID);
  if (!ch)
   throw StreamException("Channel not found");
 
  setStatus(S_CONNECTED);
 

  //这里进行最重要的数据传输,请特别注意
  LOG_DEBUG("Starting Raw stream of %s at %d",ch->info.name.cstr(),streamPos);
 
//说明几个重要的参数,
//streamPos:当前频道流所在缓冲的为止pos
//rawData为频道流缓冲区(专门用来存放数据包的地方)
//syncPos则是用来标志该媒体数据应当放在缓冲区当中的顺位,以数量为单位。用来保证节点间

//同步缓存位置的参数

  if (sendHead)
  {
   ch->headPack.writeRaw(*sock);
   streamPos = ch->headPack.pos + ch->headPack.len;
   LOG_DEBUG("Sent %d bytes header ",ch->headPack.len);
  }
 
  if (sendData)
  {
 
   unsigned int streamIndex = ch->streamIndex;
   unsigned int connectTime = sys->getTime();
   unsigned int lastWriteTime = connectTime;
 
   while ((thread.active) && sock->active())
   {
    ch = chanMgr->findChannelByID(chanID);
 
    if (ch)
    {
 
     if (streamIndex != ch->streamIndex)
     {
      streamIndex = ch->streamIndex;
      streamPos = ch->headPack.pos;
      LOG_DEBUG("sendRaw got new stream index");
     }
 
     ChanPacket rawPack;
     if (ch->rawData.findPacket(streamPos,rawPack))
     {
      if (syncPos != rawPack.sync)
       LOG_ERROR("Send skip: %d",rawPack.sync-syncPos);
      syncPos = rawPack.sync+1;
 
      if ((rawPack.type == ChanPacket::T_DATA) || (rawPack.type==ChanPacket::T_HEAD))
      {
       rawPack.writeRaw(*sock);
       lastWriteTime = sys->getTime();
      }
 
      if (rawPack.pos < streamPos)
       LOG_DEBUG("raw: skip back %d",rawPack.pos-streamPos);
      streamPos = rawPack.pos+rawPack.len;
     }
    }
 
    if ((sys->getTime()-lastWriteTime) > DIRECT_WRITE_TIMEOUT)
     throw TimeoutException();
   
    sys->sleepIdle();
   }
  }
 }catch(StreamException &e)
 {
  LOG_ERROR("Stream channel: %s",e.msg);
 }
}


改进方案:底层传进来的sock可以改用RTP进行封装,通过RTP来传输流媒体给播放器,可以改进传输的数据,如何操作:
需要在底层创建一个WSARTPClientSocket,实现的功能类似WSAClientSocket,只是我们需要的是在WSARTPClientSocket下实现的是基于UDP的RTP套接字,改装底层套接字的实现,这样子当我们穿进来一个rtpSock时,rawPack.writeRaw(*rtpSock);将会执行调用rtp来发送数据的底层实现,这样子我们可以改进数据传输的效率,不过在播放器端需要改用RTP来进行接受,那么如何实现改用RTP传输时和播放器通信的接口呢:

 

首先,数据通信方式上,第一步还是采用播放器想PeerCast进行http请求,请求方式如上
第二,PeerCast同样对该请求进行http回复,紧接着,就开始了RTP数据的传送阶段
第三,我们需要知道原先sock套接字的创建在何处,哪里边kill掉,以及哪里执行了何种方法,只有这样子我们才能创建我们的rtpSock,为rtpSock的各种实现添加同样的操作代码。
第四,播放器要做的处理主要有两方面,首先要能够进行http请求,能够处理http回复的http包,其次要有能够接受rtp数据包的相关实现机制,这是播放器需要做的两种处理

 

PeerCast在启动时,就开启了7144线程在监听进来的连接,这个地方就是sock创建的时候,我们可以将sock里面的相关数据拷贝到rtpSock中

----------------------》ClientSocket *cs = sv->sock->accept(); //创建一个新的socket以接受连接

在kill方法中我们要做rtpSock的相应处理,在reset方法也要有相应的处理