作者: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的例子看到如何修改播放格式的,支持的挺多的:)。
有什么好的想法或是建议可以联系:afterain@263.net