{分享}解决一个困扰我N久的Filter问题的过程

 本文章为深深爱你QQ66557239原创作品,转载请注明出处http://hi.baidu.com/66557239

{分享}解决一个困扰我N久的Filter问题的过程

   3月的时候,做过一个字符叠加的Filter,这个Filter只要一加入我的链路,我的视频录像就不稳定了,
运行个几分钟,就挂了。当然这是后来测试的结果,开始的时候,偶的DVR运行个几分钟就当掉,我还以为是
DVR的问题。

    Filter在出现问题的时候,一般表现为报个地址错,然后弹出个CPU窗口,CPU窗口里的一些汇编代码显
示当前在异常处理代码附近,然后,你点确定,计算机自动重新启动。只有这些信息,真汗,突然感觉到无从
下手!
  
    字符叠加Filter(名称为TitleOverlay)的功能是,在视频采集时,给视频图像加上字幕,这个字幕包含
时间、设备编号、用户自定义信息。当然,我是用在视频采集的时候,如果用在视频回放的时候,同样是可以
的。

    TitleOverlay基于COM架构设计,属于标准的COM组件。Filter属于trans filter类型,继承
TBCTransInPlaceFilter类, Filter包含ITitleOverlay接口,该接口用于接受设备编号、用户自定义信息
的输入。

    一直没有整体的时间来解决这个问题,现在正值国庆长假前夕,该做的工作,偶早已完成,终于有时间静
下心来,仔细分析研究这个问题的原因所在了。

    面对这样的问题,诚然不像一般的报错,一般IDE就直接给你定位在某行上面了,然后可以在这行代码附
近找原因了。一时间,我觉得无从下手了。按照常规想,一般就是操作内存对像的时候,比如你访问了没有创
建的对象、访问了没有分配内存空间的地址,会报地址错,所以我就考虑,会不会是创建的对象被释放了,然
后其它地方又出现了访问的现象,自然就想到了作用域的问题。

    仔细看了一下代码,确实存在这个问题。最明显的就是在一个类内声明了,但是没有说明其作用域,没有
定义其属于private、public、protected等,像这样的变量或者结构,我认为其作用域应该与public是一样
的,外界是可以访问的。但是这只是一个实例它本身的作用域问题,应该不存在我释放了,然后别人来访问的
情况,毕竟实例(这里所说的实例是指TitleOverlay对象)间是独立使用的。不过还是应该把属于类内的成员
移至private内,尽量不允许外界访问。

    既然不是作用域的问题,还有没有其它地方可能会出现实例间访问的情况呢?这时我想到了TitleOverlay
本身。TitleOverlay本身是基于COM架构的,COM组件的使用牵扯到了一个引用计数方式。在我看来,只要使用
了引用计数方式,一般都是在共用内存空间,比如我们使用string类型的变量AString、BString,那么执行
BString:=AString,事实上并没有给变量BString分配内存空间,而是在AString变量的引用计数位加1,与
AString共用内存空间。那么TitleOverlay是不是也存在大家共用一个内存空间的现象呢?

   那么,我们来看看COM组件的使用方式。COM组件的使用是通过访问接口来实现的。比如,我要使用
TitleOverlay,首先我得查找到TitleOverlay的接口IBaseFilter,然后通过IBaseFilter来调用COM组件的
成员函数。引用计数方式就是在使用接口的时候用到的,当有多个客户程序通过不同的接口使用同一个
TitleOverlay,那么每一个客户程序通过接口去使用的TitleOverlay的时候,都会在TitleOverlay的引用计数
加1,当每一个客户程序终止使用接口时,那么就会在TitleOverlay的引用计数减1,当引用计数为0时,那么
TitleOverlay对象将被释放。看来我们通过多个接口去访问同一个对象(TitleOverlay)时,在接口的地方会有
引用计数,这时候大家是共用TitleOverlay对象的。但是,在我的DVR程序内,我把一路摄像头抽象为一个类,
所有对摄像头的操作均封闭在这个类内,在使用的地方,每一路摄像头生成一个类的对象,有多少路,生成多少
个对象,如此看来,我的每个对象都有自己独立的内存空间,是不存在共用的。

   看来也不是COM组件的使用问题了。这些问题都排除了,我才发现,自己这次确实是碰到钉子了。这时候,觉得
再次无路可走了,感觉实在不行就要换功能实现的思路了。这时候,我再次回过头来,重新整理思路。

   首先,我要确定问题到底出在哪里?1.由TitleOverlay引起的;2.由DVR引起的;3.由TitleOverlay与DVR组合
使用时引起的;

   于是,我专门做了一个干净的Demo,专门用于测试TitleOverlay,其它功能概不涉及,看看TitleOverlay
到底是不是工作正常、是不是稳定?

   大约在第13分钟,熟悉的问题再次出现,现在已经可以确定问题是出在这个Filter上面了。

   TitleOverlay的代码很简单,就是字符叠加,难道是字符叠加这段代码出了问题,还是COM组件的结构写错
了?我把字符叠加的代码给屏蔽了。

   过了20多分钟,都没有报错,看来是叠加代码的问题了。

   叠加代码分为生成字符代码与真正的叠加代码,在真正叠加的部分,有较多的对内存地址的直接操作,我怀
疑是不是这个地方出错了,于是把生成字符的代码给屏蔽了,结果又过了20多钟,还是没有报错,现在可以说
问题只可能出在生成字符上面了。

   生成字符,我是在TBitmap上面直接Canvas.Textout的。由于叠加的字幕包含时间,所以每一帧图像,我都
画一次字符,然后叠加在这一帧图像上面。我现在的帧率设置的是25帧/秒,也就是一秒钟,我要画25次字符,
叠加25次,我觉得就是这个Canvas.Textout的实时性不高造成的,当然,这个在后来同
DirectShow圣地群(QQ群号:48231947)的兄弟们分享经验的时候得到了证实,有个兄弟提到,老陆的书中说过
GDI+的实时性不高。

   问题找到了,解决的方法就很多了,我采用的是降低画字符的频度,每一秒画一次字符,这样既保证了时间
显示的准确性,也避免了画字符实时性不高的问题。修改完成后,经测试,问题解决。

   在问题结束的时候,出现了一个小插曲,烦人的内存泄露问题又出现了,经检查,是摄像头对象在执行录像
动作的时候,动态创建了Filter,没有释放的原因;还有COM组件内的Bitmap在释放之前,我用了
TBitmap.ReleaseHandle,本意是想把TBitmap占用的句柄给释放掉的,没想到这个动作是不但把句柄给释放掉
了,同时也让TBitmap找不到其内存空间了,导致FreeAndNil(TBitmap)执行没有效果了。另外,对字符叠加部
分的算法也做了优化,提高的执行效率。

    OK,困扰我很久的问题终于解决,无限舒畅。通过这个问题,我更加有信心去解决一些历史问题,
同时这个过程也很有意义,于是花时间写出来,以飨读者。

本文章为深深爱你QQ66557239原创作品,转载请注明出处http://hi.baidu.com/66557239

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
//指定视频采集设备的友好名字,为它创建一个Filter IBaseFilter * CTestPreviewDlg::CreateVideoDevice(const char * inFriendlyName) { return CreateHardwareFilter(CLSID_VideoInputDeviceCategory,inFriendlyName); } //根据设备的友好名字,创建一个代表该设备的Filter IBaseFilter * CTestPreviewDlg::CreateHardwareFilter(GUID inCategory,const char * inFriendlyName) { //创建一个系统枚举组件对象 ICreateDevEnum * enumHardware = NULL; HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_ALL, IID_ICreateDevEnum,(void**)&enumHardware); if(FAILED(hr)) { return NULL; } IBaseFilter * hardwareFilter = NULL; IEnumMoniker * enumMoniker = NULL; //为指定的目录创建枚举器 hr = enumHardware->CreateClassEnumerator(inCategory,&enumMoniker,0); if(enumMoniker) { enumMoniker->Reset(); ULONG fetched = 0; IMoniker * moniker = NULL; char friendlyName[256]; //枚举得到该目录下所有的设备,逐个进行名字匹配 while(!hardwareFilter && SUCCEEDED(enumMoniker->Next(1,&moniker, &fetched)) && fetched) { if(moniker) { IPropertyBag * propertyBag = NULL; VARIANT name; friendlyName[0] = 0; hr = moniker->BindToStorage(0,0,IID_IPropertyBag,(void**)&propertyBag); //读取设备的友好名字 if(SUCCEEDED(hr)) { name.vt = VT_BSTR; hr = propertyBag->Read(L"Friendlyname",&name,NULL); } if(SUCCEEDED(hr)) { WideCharToMultiByte(CP_ACP,0,name.bstrVal,-1, friendlyName,256,NULL,NULL); //如果当前设备的友好名字与用户指定的设备名字相同, //则将当前设备标识绑定为Filter形式 if(strcmp(friendlyName,inFriendlyName) == 0) { moniker->BindToObject(0,0,IID_IBaseFilter, (void**)&hardwareFilter); } } //释放使用过的接口 if(propertyBag) { propertyBag->Release(); propertyBag = NULL; } moniker->Release(); } } enumMoniker->Release(); } enumHardware->Release(); return hardwareFilter; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值