DirectShow 源过滤器用于在 DC 上绘画






3.41/5 (9投票s)
2004 年 9 月 12 日
4分钟阅读

144297

2716
如何在源过滤器中绘制到 DC。
引言
好吧,创建DirectShow过滤器有时会很痛苦,尤其是源过滤器。当我需要一个源过滤器来生成我自己的图像时,我查看了DirectShow文档中的Ball源过滤器,但对我来说它毫无用处,因为我需要使用自己的绘画来生成自己的图像。所以,它是这样的。
这个过滤器允许您创建自己的DC并在该DC上进行所有绘画。在DC上绘制的所有内容都将被传递给下游过滤器,直到最终的渲染器过滤器渲染RGB数据。
该过滤器基于CSourceStream
来创建输出引脚并传递数据,基于CSource
来创建源过滤器。
流程
我将这篇文章写得很短,因为那些需要创建 such a filter 的人会最了解DirectShow的架构和过滤器。
这个过滤器中唯一的新内容是创建RGB类型的媒体类型,在创建流线程时创建DC,并将媒体样本填充为从DC获取的新缓冲区。
创建媒体类型
这个源过滤器生成24位RGB视频。要创建媒体类型,会重写CSourceStream
的GetMediaType
。在过滤器图连接过滤器之前,图会调用此函数,以便过滤器就媒体类型达成一致(好吧,除了分配器、媒体类型和输出引脚对连接的最终接受之外,这还是用于连接的函数之一)。此函数接收由下游过滤器输入引脚提供的源过滤器输出引脚上的CMediaType
指针。输出引脚填充它将要生成的媒体类型的信息。
//
// GetMediaType
//
HRESULT CSnakeStream::GetMediaType(CMediaType *pMediaType)
{
CAutoLock lock(m_pFilter->pStateLock());
ZeroMemory(pMediaType, sizeof(CMediaType));
// TODO: modify this option
{
VIDEOINFO *pvi =
(VIDEOINFO *)pMediaType->AllocFormatBuffer(sizeof(VIDEOINFO));
if (NULL == pvi)
return E_OUTOFMEMORY;
ZeroMemory(pvi, sizeof(VIDEOINFO));
pvi->bmiHeader.biCompression = BI_RGB;
pvi->bmiHeader.biBitCount = 24;
pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pvi->bmiHeader.biWidth = 320;
pvi->bmiHeader.biHeight = 240;
pvi->bmiHeader.biPlanes = 1;
pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader);
pvi->bmiHeader.biClrImportant = 0;
SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered.
SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle
pMediaType->SetType(&MEDIATYPE_Video);
pMediaType->SetFormatType(&FORMAT_VideoInfo);
pMediaType->SetTemporalCompression(FALSE);
const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
pMediaType->SetSubtype(&SubTypeGUID);
pMediaType->SetSampleSize(pvi->bmiHeader.biSizeImage);
m_bmpInfo.bmiHeader = pvi->bmiHeader;
}
return S_OK;
}
流线程
当过滤器图进入暂停状态时,过滤器图会调用源流的OnThreadCreate()
。此函数创建一个线程来生成数据,然后调用FillBuffer
来填充数据。
我们重写此函数以进行任何初始化。在此示例过滤器中,我绘制了一条“蛇”(有点像),它在屏幕中间从左到右爬行。在此函数中,我初始化了蛇的高度、宽度以及蛇身的方块数量。
绘画的魔力
嗯,在DC上绘画的所有魔法实际上不过是创建一个DIB节并将DC选择用于绘画。从这个DIB节中,我们提取数据并填充媒体样本。所有这些都在CSrouceStream
的OnThreadCreate()
重写中完成。整个魔法如下:
//
// OnThreadCreate
//
HRESULT CSnakeStream::OnThreadCreate(void)
{
HBITMAP hDibSection = CreateDIBSection(NULL,
(BITMAPINFO *) &m_bmpInfo, DIB_RGB_COLORS,
&m_pPaintBuffer, NULL, 0);
HDC hDC = GetDC(NULL);
m_dcPaint = CreateCompatibleDC(hDC);
SetMapMode(m_dcPaint, GetMapMode(hDC));
HGDIOBJ OldObject = SelectObject(m_dcPaint,hDibSection);
m_nScoreBoardHeight = m_bmpInfo.bmiHeader.biHeight/8;
m_nSnakeBlockHeight = 4, m_nSnakeBlockWidth = 6;
m_nNumberSnakeBlocks = 6;
m_nSpaceBetweenBlock = 1;
m_nLastX = 0;//(m_bmpInfo.bmiHeader.biWidth/2) -
// (m_nSnakeBlockWidth+m_nSpaceBetweenBlock)*m_nNumberSnakeBlocks;
m_nLastY = 0;//(m_bmpInfo.bmiHeader.biHeight/2);
return CSourceStream::OnThreadCreate();
}
将图片向下游发送
这很简单直接。一旦我们创建了一个离屏DC并将一个DIB节选择到DC中,我们所要做的就是在DC中进行任何需要的绘画,然后只需将DIB节缓冲区复制到媒体样本缓冲区即可。
所有这些都在CSourceStream
的FillBuffer
重写中完成。当上游过滤器请求数据时,在OnThreadCreate()
中创建的线程会调用FillBuffer
。该过滤器以每秒26帧的速度运行。
填充媒体样本的代码如下:
//
// FillBuffer
//
HRESULT CSnakeStream::FillBuffer(IMediaSample *pSample)
{
CAutoLock lock(m_pFilter->pStateLock());
HRESULT hr;
BYTE *pBuffer;
long lSize;
hr = pSample->GetPointer(&pBuffer);
if (SUCCEEDED(hr))
{
lSize = pSample->GetSize();
if( nFrameRate++ > 150 )
{
PatBlt(m_dcPaint,0,0,m_bmpInfo.bmiHeader.biWidth,
m_bmpInfo.bmiHeader.biHeight,BLACKNESS);
//PatBlt(m_dcPaint,0,m_bmpInfo.bmiHeader.biHeight-m_nScoreBoardHeight,
// m_bmpInfo.bmiHeader.biWidth,m_nScoreBoardHeight,WHITENESS);
RECT rcSnake;
for(int n=m_nNumberSnakeBlocks;n>=0;n--)
{
rcSnake.left = m_nLastX + (m_bmpInfo.bmiHeader.biWidth/2)
- (m_nSnakeBlockWidth)*n;
rcSnake.top = m_nLastY + (m_bmpInfo.bmiHeader.biHeight/2);
rcSnake.right = rcSnake.left + m_nSnakeBlockWidth;
rcSnake.bottom = rcSnake.top + m_nSnakeBlockHeight;
FillRect(m_dcPaint,&rcSnake,CreateSolidBrush(RGB(0,255,0)));
m_nLastX += m_nSnakeBlockWidth;
if( m_nLastX+(m_nSnakeBlockWidth+m_nSpaceBetweenBlock)
*m_nNumberSnakeBlocks > m_bmpInfo.bmiHeader.biWidth )
{
m_nLastX = -m_bmpInfo.bmiHeader.biWidth/2;
}
TRACE("%d %d %d %d\n",rcSnake.left,rcSnake.top,
rcSnake.right,rcSnake.bottom);
}
nFrameRate=0;
}
m_llFrameCount++;
CRefTime m_rtStart; // source will start here
CRefTime m_rtStop; // source will stop here
m_rtStart = m_llFrameCount*1000000/26;
m_rtStop = (m_llFrameCount+1)*1000000/26;
// Draw the current frame
TCHAR szText[256];
wsprintf( szText, TEXT("%s\0"), TimeToTimecode(m_rtStart));
PatBlt(m_dcPaint,0,m_bmpInfo.bmiHeader.biHeight-25,
m_bmpInfo.bmiHeader.biWidth,
m_bmpInfo.bmiHeader.biHeight,BLACKNESS);
SetBkMode(m_dcPaint,TRANSPARENT);
SetTextColor(m_dcPaint,RGB(255,255,255));
if( !TextOut( m_dcPaint, m_bmpInfo.bmiHeader.biWidth/2-50,
m_bmpInfo.bmiHeader.biHeight-20,
szText,_tcslen( szText ) ) )
return E_FAIL;
CopyMemory(pBuffer,m_pPaintBuffer,lSize);
}
return S_OK;
}
在上面的代码中,我们每秒绘制一次蛇,但每帧都更新帧计数器位移。CopyMemory
将DIB节的缓冲区复制到输出媒体样本。
请小心
好了,在使用代码或从中获得灵感之前,请仔细阅读代码。示例中的代码不多,我没有进行任何检查或任何其他设置。
如何使用它
这是DirectShow的好处之一。使用起来很方便(如果它工作正常),只需从Filter graph中的DirectShow过滤器列表中添加snake源过滤器即可。渲染输出引脚。完成。
还有更多工作要做
嗯,正如您在上面看到的,创建过滤器看起来非常简单,但它非常非常基础。在实际场景中,您可能需要支持过滤器的寻道功能。您可能还需要生成音频样本。然后,您可能需要支持多种时间格式来进行寻道以及同步生成的音频和视频样本。
查看DirectShow文档中的新Ball示例,它支持IMediaSeeking
,但并没有解决多种时间格式和音频视频同步的所有问题。
除了所有这些之外,大多数源过滤器都从文件中读取,可以是推送式或拉取式源过滤器,即实现IAsynReader
或IFileSourceFilter
。这又可能是一个复杂的实现。
我将尝试发布另一篇文章,介绍如何处理上述问题,在此期间,请享受编程……祝您编码愉快!
历史
- 2004年9月12日:初版
许可证
本文没有明确的许可附加,但可能包含文章文本或下载文件本身的使用条款。如有疑问,请通过下方的讨论区与作者联系。可以 此处 找到作者可能使用的许可证列表。