WEB开发网
开发学院软件开发VC VC实现卡拉OK字幕叠加 阅读

VC实现卡拉OK字幕叠加

 2010-05-25 20:36:36 来源:WEB开发网   
核心提示:三. 关键实现我们使用VC生成一个基于对话框的程序来演示卡拉OK字幕叠加的实现,为了使字幕叠加的过程更加清晰,VC实现卡拉OK字幕叠加(3),我们设计了一个逻辑控制类CSubtitleController,在进行真正的字幕叠加之前,则画出整行sungLength = mTotalWidth;}// 将字幕字体选入目标窗

三. 关键实现

我们使用VC生成一个基于对话框的程序来演示卡拉OK字幕叠加的实现。

为了使字幕叠加的过程更加清晰,我们设计了一个逻辑控制类CSubtitleController。在进行真正的字幕叠加之前,我们必须首先调用CSubtitleController类的SetTargetWindow函数设置字幕的显示窗口,随后调用SetSubtitleLine函数设置字幕行的内容、填充时间等属性。具体实现中,我们在主对话框类CKaraokeDemoDlg中定义一个CSubtitleController类的实例mController,并且在对话框的初始化函数OnInitDialog中进行了如下的调用:

BOOL CKaraokeDemoDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
mController.SetTargetWindow(&mKaraokeWnd);
mController.SetSubtitleLine(mSubtitleArray, mDurationArray, 0, 5);
// ......
return TRUE;
}
其中,mKaraokeWnd表示字幕显示窗口,是一个CStatic类的对象实例;mSubtitleArray是CString类型的数组,用于存储字幕内容(注意,应将字幕行中的各个字符单独存储);mDurationArray是int类型的数组,用于存储字幕行中各个字符填充需要的时间。mSubtitleArray和mDurationArray可以在CKaraokeDemoDlg类的构造函数中做如下的初始化:
mSubtitleArray = new CString[5];
mDurationArray = new int[5];
mSubtitleArray[0] = "真";
mSubtitleArray[1] = "的";
mSubtitleArray[2] = "好";
mSubtitleArray[3] = "想";
mSubtitleArray[4] = "你";
mDurationArray[0] = 1500; // 以毫秒为单位
mDurationArray[1] = 300;
mDurationArray[2] = 1600;
mDurationArray[3] = 500;
mDurationArray[4] = 1000;
  主对话框类中还使用了一个定时器,定时间隔是40毫秒,即以每秒25帧的频率刷新字幕叠加的进度。我们在开始播放(即当用户按下“Play”按钮)时记下系统时间(存储到DWORD类型的变量mStartTime中),然后在每次定时到达的时候再次读取系统时间,与mStartTime做差值运算,得到当前播放到的时间点(我们暂且称之为流时间)。在定时器消息响应函数CKaraokeDemoDlg::OnTimer中,我们会调用CSubtitleController类的DrawSubtitle函数来完成实际的卡拉OK字幕输出,这个函数的参数就是这个流时间。
  在CSubtitleController类中,我们看到DrawSubtitle函数的具体实现如下:
BOOL CSubtitleController::DrawSubtitle(DWORD inStreamTime)
{
ASSERT(mClientDC);
DWORD timeInChar = 0; // 相对于当前字符填充的开始时间的时间
LONG sungLength = 0; // 已经唱过的字幕宽度
// LocateChar为CSubtitleController类的一个私有函数
// 根据当前播放到的时间点,定位到当前进度中的字符,
// 并且得到播放时间点在当前字符中的相对时间
int currentChar = LocateChar(inStreamTime, timeInChar);
if (currentChar != -1) // 定位成功
{
// 计算已经唱过的字幕宽度
// mFromToArray数组记录各个字符的属性,包括开始、结束时间、尺寸等
sungLength = mFromToArray[currentChar].size.cx * timeInChar;
sungLength = sungLength / mFromToArray[currentChar].duration;
for (int i = 0; i < currentChar; i++)
{
// 累加上当前进度中的字符以前的所有字符的宽度
sungLength += mFromToArray[i].size.cx;
}
}
else
{
// 如果无法定位到任何一个字符,则画出整行
sungLength = mTotalWidth;
}
// 将字幕字体选入目标窗口的DC中
CFont * pOldFont = (CFont *) mClientDC->SelectObject(&mTextFont);
mClientDC->SetBkMode(TRANSPARENT); // 设置输出时背景透明
// 生成已经唱过的和尚未唱过的两块窗口区域
// mSungRegion和mSingingRegion均是CRgn类对象实例
mSungRegion.CreateRectRgn(mStartPoint.x, mStartPoint.y,
mStartPoint.x + sungLength, mStartPoint.y + mFromToArray[0].size.cy);
mSingingRegion.CreateRectRgn(mStartPoint.x + sungLength, mStartPoint.y,
mStartPoint.x + mTotalWidth, mStartPoint.y + mFromToArray[0].size.cy);
// 画出第一部分:已经唱过的字幕(蓝色填充,白色勾边)
int ret = mClientDC->SelectClipRgn(&mSungRegion, RGN_COPY);
mClientDC->SetPolyFillMode(WINDING);
HPEN pOldPen = (HPEN) mClientDC->SelectObject(mSungBoundaryPen);
HBRUSH pOldBrush = (HBRUSH) mClientDC->SelectObject(mSungTextBrush);
mClientDC->BeginPath();
mClientDC->TextOut(mStartPoint.x, mStartPoint.y, mSubtitleLine);
mClientDC->EndPath();
mClientDC->StrokeAndFillPath(); // 画出字符路径并填充
mClientDC->SelectClipPath(RGN_AND);
// 恢复以前的画笔和画刷
mClientDC->SelectObject(pOldPen);
mClientDC->SelectObject(pOldBrush);
// 画出第二部分:尚未唱过的字幕(黑色勾边空心字)
pOldPen = (HPEN) mClientDC->SelectObject(mSingingBoundaryPen);
pOldBrush = (HBRUSH) mClientDC->SelectObject(mSingingTextBrush);
mClientDC->SelectClipRgn(&mSingingRegion, RGN_COPY);
mClientDC->BeginPath();
mClientDC->TextOut(mStartPoint.x, mStartPoint.y, mSubtitleLine);
mClientDC->EndPath();
mClientDC->StrokePath(); // 画出字符路径(不填充)
mClientDC->SelectClipPath(RGN_AND);
// 恢复以前的画笔和画刷
mClientDC->SelectObject(pOldBrush);
mClientDC->SelectObject(pOldPen);
mSungRegion.DeleteObject();
mSingingRegion.DeleteObject();
// 恢复目标窗口为“全区域”
RECT bounds;
mTargetWnd->GetClientRect(&bounds);
CRgn rgn;
rgn.CreateRectRgn(bounds.left, bounds.top, bounds.right, bounds.bottom);
ret = mClientDC->SelectClipRgn(&rgn, RGN_COPY);
// 恢复以前的字体
mClientDC->SelectObject(pOldFont);
// 如果无法定位到任何一个字符,则返回一个错误值
return (currentChar != -1);
}
// 根据当前播放到的时间点,定位到当前进度中的字符
int CSubtitleController::LocateChar(DWORD inStreamTime, DWORD & outTimeInChar)
{
// mCharCount为整个字幕行的字符个数
for (int i = 0; i < mCharCount; i++)
{
if (inStreamTime >= mFromToArray[i].from &&
inStreamTime < mFromToArray[i].to)
{
outTimeInChar = inStreamTime - mFromToArray[i].from;
return i;
}
}
return -1;
}

上一页  1 2 3 4  下一页

Tags:VC 实现 卡拉OK

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接