作者: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;
- m_llLength = 4000000000000;
- hMutex = CreateMutex(NULL,TRUE,"protect buf");
-
接着就是Read函数了,它是自动调用的,而且是个work thread,参数pbBuffer是输出变量,就是要播放的数据,pdwBytesRead也是输出变量,表示读了的数据长度,其余是输入变量:
- HRESULT Read(PBYTE pbBuffer,
- DWORD dwBytesToRead,
- BOOL bAlign,
- LPDWORD pdwBytesRead)
- {
- CAutoLock lck(&m_csLock);
- DWORD dwReadLength;
-
- dwReadLength = dwBytesToRead;
-
-
- while (32768>m_Buf Size);
-
- WaitForSingleObject(hMutex, 1L); file:
- CopyMemory((PVOID)pbBuffer, (PVOID)m_PlayBuf,dwReadLength);
-
- ReleaseMutex(hMutex);
- m_Buf Size -= dwReadLength;
- CopyMemory((PVOID)m_PlayBuf, (PVOID)(m_PlayBuf+dwReadLength),m_Buf Size);
-
-
- m_llPosition += dwReadLength;
- *pdwBytesRead = dwReadLength;
- return S_OK;
- }
-
- //最后就是我们怎么样才能更新我们的数据呢?在这里建立个线程比较合理。下面的函数是我自己添
- //加到CmemStream类的。然后在主程序中建立线程来调用该函数。
- LONGLONG AddBuf(PBYTE buf)
- {
- if ((m_Buf Size +32768)>32768*10){
-
- return -1;
- }
- WaitForSingleObject(hMutex, 1L);
- CopyMemory((PVOID)(m_PlayBuf+m_Buf Size),(PVOID)buf,32768);
- ReleaseMutex(hMutex);
- m_Buf Size += 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)
- {
- eof = f.Read(buf,32768);
- if(eof!=32768)
- break;
-
- while(STREAM_SendBuf(buf)==-1);
- }
- f.Close();
- return 0;
- }
NOTE:
要注意的是,这个类本来是异步读文件的,受到天生的限制,用于实时播放是不太适合的(特别是当你的数据量相对32768来说,如 果是5秒的话,是不能接受的。我的项目就是能够调节数据量,当最大时,延时小,只有1秒,数据量小时超过5秒)。如果用于实时的话,它只适合局域网或是宽 带网。
当然,如果只是作为网络播放文件还是比较好的。如果网络速度低的话,还是传输MPEG-4 的好。默认是MPEG-1格式的,可以通过memfile的例子看到如何修改播放格式的,支持的挺多的:)。
有什么好的想法或是建议可以联系:afterain@263.net
(afterain) |