天天看點

Peercast緩沖和媒體流管理

       開始寫點東西,給自己寫----《勸學》篇有雲:君子博學而日參省乎己,則智明而行無過矣。

我不是君子,也不博學,希望能日參省乎己,不求無過,但求能明智。

     看了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;

 }