網絡播放器

作者:afterain
本人最近剛剛把它做完。鑑於現在很多 人在向這方面發展,所以決定把自己 在此期間的一些經驗寫出來。讓後來的同志們少走些彎路。
我的這個事例是通過directshow的例子memfile改寫的。如果用於網絡的時時播放,會有一些延時問題。具體會在後面說明。我已經把它作成了DLL(實際也是工作的需要 :) ),大家可以在www.feelby.net下載。包括演示例子的源代碼。至於DLL中的其他代碼,可以參考我原來的文章,可在CSDN的開發文檔中找到(關鍵字用“direct”),說明了一些directshow的基本知識和對他的操作。


先說說memfile例子的整體框架。實際上,directshow已經封裝好了幾個類,CasyncReader和CasyncStream是我們最關心的,CasyncReader已經是個source filter了,而我們只需通過CasyncStream類就可以控制數據了。CasyncStream類很簡單,都是一些純虛函數。我們是繼承它,把它的函數完善就行了。


現在把工作的重點放在CasyncStream類。Memfile是繼承了它得到自己的類CmemStream。因爲這個類有了一些函數的總體框架,所以我就用它做爲父類了(當然,完全可以直接從CasyncStream繼承)。有三個重要的函數:SetPointer(LONGLONG llPos),Read(PBYTE pbBuffer,DWORD dwBytesToRead,BOOL bAlign,LPDWORD pdwBytesRead)和Size(LONGLONG *pSizeAvailable)。其中重中之重是Read函數,(實際上我已經棄用SetPointer函數了)。所以的數據操作都是在這裏完成的。下面通過具體的代碼來說明。


參數的說明:
m_pbData                     讀寫的內存數據指針
m_llLength                   數據的總長度
m_llPosition                 實際讀寫的內存數據位置指針
m_dwKBPerSec          播放的的速率
由於初期時,操作內存數據指針m_pbData總是出錯,所以改用自己的指針。本來是打算用m_llPosition來虛擬個無限大的內存空間(實際就是循環0---max,讀前面的數據,刷新後面的,接着讀後面的,刷新前面的來達到這效果),可是要煩瑣一些,有些臨界條件難以判斷。所以實際上是我只用了它的參數m_llLength。大家可以通過memfile的源代碼來學習m_llPosition+ m_pbData的用途。
m_llLength是個非常重要的參數。如果你要做網絡的實時監控,當然不希望播放了幾個小時就停了。通過修改它可以達到你們需要的長度。它是LONGLONG型的,就是說2的64次方。足夠播放n年了 : ) 。
NOTE:如果你把它改的小,不論你怎麼添加內存數據都不能持續的播放。directshow播放完這個長度的數據後就自動的停止了。
首先,初始化參數:
m_PlayBuf = new BYTE[32768*10];//我自己定義的數據緩衝
m_Buf Size = 0;//緩衝區未播放的數據大小,開始沒數據,當然爲0
m_llLength = 4000000000000;//這個大小足夠播放了,4T的數據
hMutex   = CreateMutex(NULL,TRUE,"protect buf");//這是個HANDLE型的,用於播放和添加數據時,保護數據的完整性


接着就是Read函數了,它是自動調用的,而且是個work thread,參數pbBuffer是輸出變量,就是要播放的數據,pdwBytesRead也是輸出變量,表示讀了的數據長度,其餘是輸入變量:
HRESULT Read(PBYTE pbBuffer,
DWORD dwBytesToRead,
BOOL bAlign,
LPDWORD pdwBytesRead)
{
   CAutoLock lck(&m_csLock);
   DWORD dwReadLength;


   dwReadLength = dwBytesToRead;//只有在最後的數據包改寫該參數(因爲不一定會符合32768的大小,我默認不修改)
   ///////////////////////////handle buf
   while (32768>m_Buf Size);//wait for add  new data


   WaitForSingleObject(hMutex, 1L); file://這一小段是關鍵
   CopyMemory((PVOID)pbBuffer, (PVOID)m_PlayBuf,dwReadLength);//從我們的緩衝中得到要播放的數據
   ReleaseMutex(hMutex);
   m_Buf Size -= dwReadLength;//未播放的數據大小減去剛剛播放的數據量dwReadLength
   CopyMemory((PVOID)m_PlayBuf, (PVOID)(m_PlayBuf+dwReadLength),m_Buf Size);//把未播放的數據移動到m_PlayBuf的開頭,這樣我們就不需要位置指針m_llPosition。這樣有個好處,就是,m_llPosition實際上播放是會有一次從229376返回0,所以在播放位置的判斷很麻煩,這就是爲什麼我棄用這個參數的原因。


   m_llPosition += dwReadLength;//這是memfile的代碼,我沒有去掉
   *pdwBytesRead = dwReadLength;
   return S_OK;
 }


最後就是我們怎麼樣才能更新我們的數據呢?在這裏建立個線程比較合理。下面的函數是我自己添加到CmemStream類的。然後在主程序中建立線程來調用該函數。
LONGLONG AddBuf(PBYTE buf)
{
   if ((m_Buf Size +32768)>32768*10){//當添加的數據超過了我們的緩衝大小,則返回-1告訴調用程序,具體的處理由調用程序決定,是丟棄還是重發
    return -1;
   }
  
   WaitForSingleObject(hMutex, 1L);
   CopyMemory((PVOID)(m_PlayBuf+m_Buf Size),(PVOID)buf,32768);//把新的數據添加到我們自己的緩衝中
   ReleaseMutex(hMutex);


   m_Buf Size += 32768;//未播放的數據增加 了32768個字節
   return m_Buf Size;
  }


NOTE:上面多次有32768這個數字。這是它默認的數據大小,(修改可不容易,還不如自己寫個新的source filter)。這就是我最開始提到的“延時問題”的問題的關鍵。因爲一定需要32768字節的數據才能播放,所以32768就是我們延時的數據大小,你一定要等到數據增加到32768才能給出播放。如果32768對你的壓縮數據來說,是一秒的數據,那麼就延時一秒,如果是3秒的數據量,那麼就延時3秒。這是這個類的限制。要想真正的實時,還是自己寫source filter吧。我看過一個實時的產品,它好象有自己的compress 和uncompress filter等。
在網絡的實時播放時,最重要的是數據的同步(得到新數據和播放之間),也就是Read()函數這個線程和AddBuf()函數這個線程之間的同步。如果AddBuf過快,數據就會丟失,過慢,則造成播放速度緩慢。我對多線程不是很有研究,所以我的播放事例只是簡單的重發。當然你也可以丟包,不過,可以看到播放的效果就不行了。我也有代碼同步,只是簡單的Sleep()一段時間。是實際測試出來的(本地文件播放,只是演示。實際本地文件播放,只需重發包就行了,不會造成數據包的丟失)。也用與網絡的播放,實測是1-2秒的時間延時。補充一點,數據包的丟失並不會造成播放的中斷,只是畫面上的停頓。


演示例子的說明
只是個簡單的事例。很多的代碼沒有整理。
其中最主要的的是建立一個線程AddBufThreadProc。不斷的添加數據。
DWORD  AddBufThreadProc(LPVOID p)
 {
 CFile f;
 PBYTE buf = new BYTE[32768];
 int   eof = 0;
 f.Open("e://R-161936-0600.mpg",CFile::modeRead);
   while(1)//add buf
   {
    eof = f.Read(buf,32768);//get new data,you can change it,example used socket
    if(eof!=32768)//if data finish
     break;
//    Sleep(175);//this way,if add too quick,maybe can lost data
    while(STREAM_SendBuf(buf)==-1);//this way,not lost data
   }
   f.Close();
   return 0;
  }


NOTE:
要注意的是,這個類本來是異步讀文件的,受到天生的限制,用於實時播放是不太適合的(特別是當你的數據量相對32768來說,如果是5秒的話,是不能接受的。我的項目就是能夠調節數據量,當最大時,延時小,只有1秒,數據量小時超過5秒)。如果用於實時的話,它只適合局域網或是寬帶網。
當然,如果只是作爲網絡播放文件還是比較好的。如果網絡速度低的話,還是傳輸MPEG-4 的好。默認是MPEG-1格式的,可以通過memfile的例子看到如何修改播放格式的,支持的挺多的:)。
有什麼好的想法或是建議可以聯系:[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章