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

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方法也要有相应的处理

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Peercast注释版是我在做本科毕业设计时分析Peercast的一些成果,当时阅读代码的时候做了注释,然后写了一些分析文章。本来想发到自己的博客上保留一份记录的,不想后来竟成了很多人参考Peercast的资料。这点上我是有些惭愧的,因为很多不成熟的想法和不正确的观点可能会误导大家,也希望大家能够分辨吧。本来想形成一份比较完整的注释版,但后来由于毕业以及转变研究方向等因素已经没有继续做下去了。不过核心的代码我想应该还是比较清晰的,大家可以参考一下。我把Peercast注释版发到CSDN的资源上,大家可以访问http://download.csdn.net/hicsdn/bbisonic进行下载,以后我就不回复索要代码的留言和邮件了。也是很抱歉,有半年的时间没更新过此BLOG了,对于有些我没注意到留言或邮件而漏发代码的朋友们,在此说声SORRY了。<br><br>大家在看代码前最好先看看Peercast源码分析文章,里面的<源码编译方法>可以告诉你如何编译Peercast源代码。<看源代码前必读>可以告诉你项目的组织结构以及各源码目录各自完成什么功能,可以让你对整体有个大概的了解。<Peercast整体架构分析>让你能大致了解一下Peercast的架构及设计思想。<阅读Peercast源码的一些经验>是我阅读Peercast源码的一些体会,希望对你能有所帮助。<Peercast服务器端代码执行流程>解释服务器启动的一些关键代码,<Peercast播放模块分析>解释Peercast如何调用播放器实现媒体播放的。以上是一些比较重要的文章,其他文章可以有选择性地看看。其他的话就看源码注释吧。<br><br>这份源码注释有很多不足之处,是我学习过程中的一个成果,大家请见谅。如果它能给你阅读 Peercast的过程中带来一些帮助的话,我将感到非常荣幸。<br>特此声明。<br><br>王浩聪<br>2007.12.11<br>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值