现在的音视频文件大都有字幕文件,那么播放器是如何显示这些字幕文件的呢?
下面是笔者分析MPC代码的过程:
1.class Rasterizer;
光栅操作类
Rasterizer::Draw()是最终画图的方法。
2.class CSimpleTextSubtitle : public CSTSArray;
实现了对各种不同字幕文件的读写
3.class CRenderedTextSubtitle : public CSimpleTextSubtitle,
public ISubPicProviderImpl,
public ISubStream;
实现了字幕流及各种不同字幕文件的处理。
4.class CSubtitle : public CList<CLine*>
实现了文字的各种特效处理。
5.CDirectVobSub::put_TextSettings()
设置字体的各种样式,如字体、字符集、是否斜体、高度、权重、是否画删除线、是否画下划线、字体颜色、是否有阴影、是否有轮廓。
CVideoProcFilter::put_Placement()
设置字幕显示位置,为百分比。
CDirectVobSub::put_TextBorderStyle()
设置边缘风格,是轮廓还是不透明的方框、边缘宽度、阴影深度
CDirectVobSub::put_TextFontStyle2()
设置倾斜角度、宽度比例、高度比例、文字间距
CDirectVobSub::put_TextColor()
设置主要、次要、轮廓、阴影的颜色和透明度。
6.SubPicDesc结构体中的type为颜色空间,如:MSP_YUY2,MSP_RGB16等。
bits为帧数据。
7.CMemSubPic::AlphaBlt();
进行alpha混合;
8.GetTextExtentPoint32W()返回的一个汉字的宽度为1115,原因是在CRenderedTextSubtitle类的构造函数中调用了SetMapMode(g_hDC, MM_TEXT)。
9.new CText的实例,然后调用CLine的AddTail()将CText的实例添加到CLine中;
调用Cline::PaintShadow()画阴影;
调用Cline::PaintOutline()画轮廓;
调用Cline::PaintBody()画主体;
代码如下所示:
CWord* w = new CText(style, str.Mid(i, j-i), m_ktype, m_kstart, m_kend);
CLine* ret = new CLine();
if(!ret) return(NULL);
ret->AddTail(w->Copy());
delete w;
bbox2 |= l->PaintShadow(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
bbox2 |= l->PaintOutline(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
bbox2 |= l->PaintBody(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
10.void CSimpleTextSubtitle::Add()将字幕文字添加到队列中;
void CRenderedTextSubtitle::ParseString()将一个长的字幕分解成一行或多行;
11.在CVideoProcFilter::Transform(IMediaSample* pIn)中获取一个ISubPic的实例,然后调用其方法AlphaBlt()将字幕画到帧上,
如下面的代码所示:
if(SUCCEEDED(m_pSubPicQueue->LookupSubPic(CalcCurrentTime(), &pSubPic)) && pSubPic)
{
CRect r;
pSubPic->GetDirtyRect(r);
if(fFlip ^ fFlipSub)
spd.h = -spd.h;
Debug_LOG("LookupSubPic ok");
pSubPic->AlphaBlt(r, r, &spd);
}
CVideoProcFilter::InitSubPicQueue()
{
CComPtr<ISubPicAllocator> pSubPicAllocator = new CMemSubPicAllocator(m_spd.type, CSize(m_w, m_h));
pSubPicAllocator->SetCurSize(window);
pSubPicAllocator->SetCurVidRect(CRect(CPoint((window.cx - video.cx)/2, (window.cy - video.cy)/2), video));
//下面的两行代码为陈丹宇添加,测试能否获取一个ISubPic *对象
CComPtr<ISubPic> pStatic;
if(FAILED(pSubPicAllocator->GetStatic(&pStatic)))
break;
}
class CMemSubPicAllocator : public ISubPicAllocatorImpl;
STDMETHODIMP ISubPicAllocatorImpl::GetStatic(ISubPic** ppSubPic)
{
if(!ppSubPic)
return E_POINTER;
if(!m_pStatic)
{
if(!Alloc(true, &m_pStatic) || !m_pStatic)
return E_OUTOFMEMORY;
}
m_pStatic->SetSize(m_cursize, m_curvidrect);
(*ppSubPic = m_pStatic)->AddRef();
return S_OK;
}
CMemSubPicAllocator::Alloc(bool fStatic, ISubPic** ppSubPic)
{
if(!(*ppSubPic = new CMemSubPic(spd)))
return(false);
(*ppSubPic)->AddRef();
}
12.从CMemSubPic::AlphaBlt()中的src和dst值可以看出,字幕位图的大小是与图像帧的大小一样的。
同时,从如下代码
if(s2[3] < 0xff)
{
d2[0] = (((d2[0]-0x10)*s2[3])>>8) + s2[1];
}
可以看出字幕位图是32位位图数据,其第四位(s2[3])为alpha通道,如果该值等于0xff则该像素为透明的,否则将当前像素的值覆盖目标位置像素的值;
13.CVideoProcFilter::Transform()中的pSubPic->AlphaBlt(r, d, &spd);
CMemSubPic::AlphaBlt(RECT* pSrc, RECT* pDst, SubPicDesc* pTarget);
将当前字幕画到目标帧中。
而当前字幕的数据又是怎么得到的呢?
CSubPicQueue::ThreadProc() ->
RenderTo(pStatic, rtStart, rtStop, fps); ->
CRenderedTextSubtitle::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox) ->
bbox2 |= l->PaintShadow(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
bbox2 |= l->PaintOutline(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
bbox2 |= l->PaintBody(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
14.显示字幕的步骤:
(1).构造字幕的描述信息SubPicDesc,然后调用new CMemSubPic构造出一个ISubPic的实例;
(2).构造样式STSStyle;
(3).调用如下代码创建字幕:
CWord* w = new CText(style, str, 0, 0, 0);
CLine* l = new CLine();
if(!l) return(NULL);
l->AddTail(w->Copy());
调用如下函数分别画阴影、轮廓和文字:
bbox2 |= l->PaintShadow(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
bbox2 |= l->PaintOutline(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
bbox2 |= l->PaintBody(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
(4).构造目标帧的描述信息SubPicDesc,并调用CMemSubPic::AlphaBlt(RECT* pSrc, RECT* pDst, SubPicDesc* pTarget)
将字幕画到目标帧中;