Peercast缓冲和媒体流管理

来源:互联网 发布:推荐一本自学java的书 编辑:程序博客网 时间:2024/05/02 04:51

       开始写点东西,给自己写----《劝学》篇有云:君子博学而日参省乎己,则智明而行无过矣。

我不是君子,也不博学,希望能日参省乎己,不求无过,但求能明智。

 

     看了PeerCast代码,感觉还是有点收获。多亏了bbisonic老大的文章,让我这个小菜收益颇多,非常感谢bbisonic兄。我只想写点自己感觉有用的东西。

      我认为Peercast中主要有3个缓冲:rawData,inData,outData。它们都是ChanPacketBuffer类型的,ChanPacketBuffer是包含了一定数目的ChanPacket(频道数据包)的类型,它是一个循环的缓冲。rawData是Channel类的成员,用于存储频道的语音视频数据,Channel类中的streamPos用于记录当前输入媒体流的位置,Servent类中的streamPos用于记录输出的媒体流位置;inData和outData是PCPStream的成员,inData用于读取PCP数据包(peercast中节点间的频道数据,包括媒体数据。。。),outData用于Peercast向外发送非媒体数据的频道或节点信息时的缓冲(如广播包、退出消息)。

      ChanPacket类中有包长,和包的位置信息(流位置);ChanPacketBuffer类包含:firstPos表示第一个数据包在缓冲中的位置(一个int型索引);lastPos表示最后一个数据包的位置(最新的媒体包);readPos表示当前读取的数据包的位置;writePos表示当前可以写入的位置(lastPos的下一个位置)。

    Peercast就是借助以上介绍的几个变量来对流媒体数据进行管理的,因为是TCP传输,所以要控制每个数据包的长度,然后在读取的一方才可能分割出每个媒体数据包进行播放。

      先说rawData,它缓冲的是媒体数据包。涉及到它的操作可以明显的分为2部分:从数据源获取媒体数据包到缓冲、发送数据包给请求的节点(=.=就是读和写),读入数据时,输入频道流位置Channel::streamPos要加上读入的数据的长度(不是数据包的长度),lastPost会加1,表示最新的包的位置,firstPos也跟着加1,writePos也加1,指向下一个包的位置。比如说节点A广播频道JOQR,A从shoutcast读取流媒体数据到rawData中,然后通过PCP协议发送给节点B。A节点不断的从shoutcast获取媒体数据,加入到循环缓冲中。A节点维护了这个频道的信息,使得该频道的streamPos指向最新的媒体流位置,lastPos指向缓冲中最新的数据包,维持这样一个状态,等到有节点请求,产生新的Servent类对象来处理。如果没有指定请求的流位置,它会先把频道的streamPos读取到Servent对象的streamPos字段,否则把最老的包的位置赋值给Servent对象的streamPos。然后发生给请求者当前的频道流位置,经协商后Servent对象从rawData中查找对应流位置的数据包,发送给请求者,把最新的媒体数据发生给B(比如A广播一首歌,B开始收听时就可以从A播放到的位置开始收听)。

 

      协商的过程使用PCP协议,B节点接受A节点的媒体数据也用的PCP协议,A节点是在函数sendPCPChannel函数中发送数据的,把频道包的包头各个字段和数据段序列化发送出去,B节点受到数据后在PCPStream::readPacket函数中,先调用id = atom.read(numc,numd);读出PCP协议的信息(id已经读出来,还有id嵌套的子字段的个数,和数据长),然后pack.len = patom.writeAtoms(id, in, numc, numd);这句把整个频道数据包读取到本地ChanPacket pack变量;在把这个包存入inData中,接下来就判断inData中是否有数据,有的话就取出来,交给procAtom函数处理,而procAtom会把频道数据包的各种信息解析出来,如果是每天数据,它就会打包存入rawData缓冲中,本地播放时再从rawData读取。在PCP协议交互的时候,A就会告诉B当前频道流的位置,B的道A的streamPos后,在后面的媒体数据的读取中,就会读取当前流位置之后的数据,而每个媒体数据包都有个pos字段记录自己在频道流中的位置,B端就会根据频道流的位置和各个包的流位置,判断媒体数据的循序和完整性,而outData和媒体数据没有关系,它只和节点间的消息交互有关,比如节点退出时要告诉它的关联节点,这时候就会组装一个广播包,放入到outData中,在广播出去。

      写的真没耐心啊,完全是自己大脑里的流水账,哈哈!但愿以后我能看懂。。。

 

 说一下ID4 AtomStream::read(int &numc,int &dlen)和AtomStream::int  writeAtoms(ID4 id, Stream &in, int cnt, int data)函数,因为bbisonic的注释里说这个不好理解,我也是看了好久才理解的,因为PCP协议是树状的,而经过TCP发送,会把所有信息序列化到一个缓冲中,要把这个树还原,就要递归读取。

比如每天数据包是这样的:

PCP_CHAN 2 PCP_CHAN_ID 16 chanID.id PCP_CHAN_PKT 3 PCP_CHAN_PKT_TYPE 4 data PCP_CHAN_PKT_POS 4 rawPack.pos PCP_CHAN_PKT_DATA rawPack.len rawPack.data

 

 

 

当然数据流里面是没有空格的,PCP协议的数据时这样的:id: len: data 或 id: len

前者表示这是一个没有子节点的节点,后者表示这是个父节点,它有len个子节点

 

AtomStream::read(int &numc,int &dlen)就是读取第一个节点的信息,向上面数据,该函数返回PCP_CHAN,就是这个节点的第一个字段的内容,numc为2表示他的子节点数,dlen为0,因为是父节点,数据段长度为0,

 

然后交由writeAtoms(ID4 id, Stream &in, int cnt, int data)函数处理

 //不好理解
 int  writeAtoms(ID4 id, Stream &in, int cnt, int data)
 {
  int total=0;

  if (cnt)
  {
   writeParent(id,cnt);//先把父节点信息写入ChanPacket 对象
   total+=sizeof(int)*2; //统计数据长



   for(int i=0; i<cnt; i++)//循环处理子节点
   {
    AtomStream ain(in); //使用AtomStream对象

    int c,d;
    ID4 cid = ain.read(c,d);//再次调用read函数,这次读的是子节点的信息
    total += writeAtoms(cid,in,c,d);//递归调用,写入的是子节点信息
   }


  }else
  {
   total += writeStream(id,in,data); //写入没有子节点的节点
  }



  return total;
 }

原创粉丝点击