DirectVobSub(VsFilter)的基本原理和实现实现

VsFilter是一个字幕叠加的库,目前由MPC-HC维护,其最新版本为2.41,本文即基于此版本。
从实现层面上来说,其内部是一个DirectShow的Transform Filter,负责将字幕文件中的字幕转换成bitmap并叠加在从输入Pin进入的图像,并交给输出Pin。
线程结构:

VsFilter接口是线程安全的,主要包含两个线程:
1.文件监控线程:CDirectVobSubFilter::ThreadProc,监控文件的修改,如果文件状态变化,需要重新加载;
2.字幕图片生成线程:CSubPicQueue::ThreadProc,CSubPicQueue内部维护一个队列,字幕文件加载后,立刻转换10条字幕成图片,放入该队列,每消耗一个图片,则立即补充一个,直到字幕播放结束。

主要模块:
1.CDirectVobSubFilter:Filter的实现,提供Filter接口实现、Pin的基本操作、Transform函数实现(也就是叠加)、文件监控线程; 2.SubPic:字幕图片缓存维护,CSubPicProviderImpl数据提供者接口; 3.Subtitles:字幕到图片转换的实现算法,主要包含: 1)STS:CSimpleTextSubtitle,加载字幕文件获得字幕信息条目STSEntry; struct STSEntry { CStringW str; bool fUnicode; CString style, actor, effect; CRect marginRect;   int layer; REFERENCE_TIME start, end; int readorder; }; 2)STS:CRenderedTextSubtitle,派生自STS:CSimpleTextSubtitle,从字幕信息条目STSEntry生成并维护字幕对象CSubtitle,CSubtitle(派生自CAtlList<CLine*>)->CAtlList<CWord*>->CPoint,也就是维护了字幕形状的基本信息;
3)Rasterizer:光栅化(bitmap生成),CWord从其派生,将字的形状信息转化成像素,从而可以叠加到图片上。
主要流程:
1.加载VsFilter.dll,创建VsFilter的com实例,并获取IID_IDirectVobSub接口,此时将创建文件监控线程;
2.连接Source Filter、VsFilter、Render Filter,此时将创建字幕图片生成线程;
3.调用IID_IDirectVobSub接口的put_FileName方法,设置字幕文件,VsFilter将加载该字幕文件,同时字幕图片生成线程将建立字幕图片缓存;
4.Filter Graph开始工作后,CDirectVobSubFilter::Transform函数获得输入Sample以及时间戳,通过输入sample的时间戳查找SubPic缓存队列中的图片,如果查不到,则从Entry中查找并生成bitmap,然后将字幕bitmap与输入sample的surface进行叠加(alphablt),叠加完成进行适当的转换(转成YUY2),然后拷贝到输出Sample。SubPic缓存队列维持长度为10,每消耗一条,则补充一条。
关键的数据流程:
1.CSimpleTextSubtitle::Open,获得CAtlArray<STSEntry>,加载字幕文件;
2.CRenderedTextSubtitle::GetSubtitle,获得CAtlMap<int, CSubtitle*> m_subtitleCache,将字幕信息转化成字幕图形信息CSubtitle;
3.CSubPicQueue::ThreadProc->CSubPicQueueImpl::RenderTo->CRenderedTextSubtitle::Render->CLine::PaintOutline->CWord::Paint->CText::CreatePath()
        BeginPath(g_hDC);
        TextOutW(g_hDC, 0, 0, m_str, m_str.GetLength());
        EndPath(g_hDC);
->Rasterizer::ScanConvert->Rasterizer::Rasterize
通过BeginPath、TextOutW、EndPath这3个GDI函数获得文字的路径点集合,并以这个路径为基础构建轮廓、阴影等像素,从而实现光栅化。
4.CSubPicQueue::EnqueueSubPic,缓存生成的bitmap图片;
5.CDirectVobSubFilter::Transform->CSubPicQueue::LookupSubPic->CMemSubPic::AlphaBlt,Source Filter推送过来一个Sample,通过输入sample的时间戳从bitmap缓存中查找对应的bitmap,然后进行透明度混合。
如过是YV12输入,有以下步骤:
Y分量叠加:
if (s2[3] < 0xff) {
d2[0] = (((d2[0] - 0x10) * s2[3]) >> 8) + s2[1];
 }
U、V分量叠加:
unsigned int ia = (s2[3] + s2[3 + src.pitch] + is2[3] + is2[3 + src.pitch]) >> 2;
if (ia < 0xff) {
*d2 = BYTE((((*d2 - 0x80) * ia) >> 8) + ((s2[0] + s2[src.pitch]) >> 1));
}
6.CBaseVideoFilter::CopyBuffer(如果需要则先进行必要的转换,如BitBltFromI420ToYUY2)->CTransformOutputPin::Deliver,拷贝叠加后的图片到输出sample。
总结:
VsFilter通过获取输出的文字路径点集合,转化成形状,并光栅化成bitmap像素,然后与输入的图像进行alpha混合,达到字幕叠加的目的,实际上是图像叠加。所有的操作都是使用CPU进行计算,所以会明显增加CPU的开销。

猜你喜欢

转载自blog.csdn.net/sonysuqin/article/details/54019985