您的位置:首页 > 其它

《微软:DirectShow开发指南》第11章 Using the Sample Grabber Filter

2013-02-23 21:25 357 查看
Although many Microsoft DirectShow applications will benefit from the creation of a transform filter, transform filters can be difficult to test. Often, a programmer needs to construct a second filter just
to test the output from his first one. The DirectShow Sample Grabber filter answers this need, although as we will see in this chapter, it can be used for much more than testing purposes.
尽管DS应用程序能从新建的转换filter中获益,但是它不好测试。通常程序员需要构建第二个filter来测试它的输出。DS的Sample Grabber (样本提取)filter可能解决这个问题,当然它的功能不止于此。

The Sample Grabber is a standard transform filter that, in itself, does nothing to the media samples that pass through it. However, it exposes the ISampleGrabber
interfaces to any DirectShow application that instantiates it. This interface allows you to “hook” your application into the media stream as it passes through the Sample Grabber. You can use the Sample
Grabber to examine output from nearly any upstream filter because there are few restrictions on where the Sample Grabber is placed within the media stream. (It is neither a source nor a renderer filter, however, and any filter graph with the Sample Grabber
must have both source and renderer filters.) The Sample Grabber allows a DirectShow application unrestricted access to the stream of media samples, in any media format that can be generated by a filter graph.
样本提取filter是一个标准的转换filter,它没有对媒体样本本身做任何处理。但是,它对所有的DS应用程序实例化了ISampleGrabber接口,这个接口允许将应用程序在样本在经过Sample Grabber时“钩住(hook)媒体流。因为对Sample Grabber 在媒体流的安放位置几乎没有限制,所以Sample Grabber可以用来检查任何上游filter的输出。(它的位置不能是源filter, 也不能是渲染filter。然后,任何带有Sample
Grabber的filter graph必须即有源,也有渲染filter) Sample Grabber允许DS应用程序不受限制地访问由filter graph生成的任何媒体格式的媒体样本流。

NOTE: 钩子(Hook),计算机用语,最早出现在操作系统中出现的钩子(Hook)概念。在Windows中,Hook是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

The Microsoft DirectX SDK actually contains two filters that expose the filter graph’s media samples to an application: the Sample Grabber, a pre-built DirectShow filter; and the Grabber
Sample, a sample Microsoft Visual C++ project. Although both perform essentially the same function, there are some interface differences between them, which would be reflected in the implementation of an application that used one or the other. In the first
half of this chapter, we’ll examine how you could use the Sample Grabber in a DirectShow application. In the second half, we’ll explore the construction of the Grabber Sample and learn how a filter can connect to your applications.
MS DirectX SDK实际上包含有两个显露filter graph的媒体样本给应用程序的filter:
Sample Grabber,它是一个预先创建的DS filter;
Grabber Sample, 一个MS VC++项目的例子。
尽管这两者本质上是同一个函数,但是它们还是一些接口上的差异,这种差异也会因为用了哪种接口而反映到应用程序实现上的不同。本章的前半部分,将介绍在一个DS应用程序中使用Sample Grabber。后半部分则是介绍Grabber Sample的创建和如何在应用程序中连接这个filter。

The Sample Grabber delivers a powerful capability that can be employed in at least two ways. First,
the Sample Grabber provides a mechanism with which on-the-fly analysis of a media stream can be performed. You can “peek” into a stream as it flows across the filter graph. For example, you might want to get audio levels from
an audio track, so you could track the loudest sounds in a recording. Second, the Sample Grabber allows you to perform in-place transformations on the sample data. As long as the data that the Sample
Grabber sends to you is returned in the same media sample, the Sample Grabber doesn’t care what the application does with it. So, the application could “silence” audio data, replace a video stream with a test pattern, or,
in the case of the Histogram application examined in this chapter, perform an analysis of each video frame, adjusting the visible qualities of the image. You can create an application with all the capabilities of a DirectShow filter (within the limits of an
in-place transform) without having to create a filter.
Sample Grabber至少有两种方式来提供它强大的功能。
首先,Sample Grabber提供了一个对媒体流进行动态分析的机制,它可以窥视流经filter graph的媒体
流。例如,如果想获得音轨的电平值 ,就可以使用Sample Grabber来跟踪样本中的最大音量。
其次,Sample Grabber允许样本数据的原地(即在pin的分配器中)转换。只要Sample Grabber发送给应用程序的数据以相同的媒体样本返回,则Sample Grabber并不关心应用程序怎么用这些数据。因此,应用程序可以关闭音频数据,换上一段测试用的视频流,或者如本章的柱状图检查例子中,执行对每个视频帧的检查,调整图像质量。通过它,可以不用创建filter而让应用程序实现所有的DS filter的功能。

When dropped into a filter graph, such as the one created by the Histogram application, the Sample Grabber looks just like any other transform filter, as shown in Figure 11-1.
在下图中的柱状图应用程序的filter graph中,Sample Grabber就如同一个转换filter。如图11-1。



Figure 11-1. The Sample Grabber filter, which fits into the media stream as a normal transform filter

However, this filter graph doesn’t tell the whole story because data is flowing from the Sample Grabber up to the Histogram application and back down again, as shown in Figure 11-2.
实际上,上图的filter graph图示说明并不完整。因为流经Sample Grabber的数据会上行结柱状图应用程序后会要返回。如图11-2所示:





Figure 11-2. The Sample Grabber passing media samples to the Histogram application for processing
Sample Grabber

Every time the Sample Grabber receives a media sample to process, it signals to the Histogram (through a
callback routine registered by the Histogram application when the Sample Grabber is instantiated) that a
sample is ready. The Histogram processes the sample during the callback and returns control to the Sample
Grabber when complete. The media sample is then presented on the output pin of the Sample Grabber and
continues downstream.
每次Sample Grabber收到一个媒体样本后,它就发送信号给柱状图应用(当Sample Grabber注册后,就可以通过注册到柱状图应用程序的回调例程来实现):样本已准备好。在回调时,柱状图处理这个样本,完成后将控制权返回给Sample Grabber。然后媒体样本表示在Sample Grabber的输出pin上以给下游。

Exploring the Histogram Application
The Histogram application transforms an incoming stream of video samples. The word histogram means that the application counts the frequency of every pixel value in each frame (or image) of the video
stream. The application tracks how often a given pixel value occurs in a given frame. Once that information has been gathered, it’s put to use in one of the following three specific ways, depending on which section of code
is executed (which is determined by commenting/uncommenting code):
柱状图应用程序转换输入的视频样本流。柱状图的意思是应用程序计数视频的每一帧的每个像素值的频率。这个应用程序用来跟踪给定帧中给定像素值的出现次数。一旦这个信息收集后,依赖于代码的哪个部分被执行,它可以有下列三种方式的应用:
A histogram stretch changes the values of each pixel in each frame so that the pixels utilize an entire range of possible values. A histogram stretch has the visible effect of changing the brightness
of the image, and it’s also known as automatic gain control. (The upper and lower range values can be set within the code.)
直方图拉伸:改变每帧中的每个像素值以使像素处理一个整体可能的范围。柱状图对图像亮度的改变有很明显的效果,它同样也被称为自动增益控制。

A histogram equalization brings out details in an image that might otherwise be invisible because they’re too dim or too bright.
直方图均衡:它可以突出图像的细节,

A logarithmic stretch applies a logarithm operator to every pixel in the frame, which boosts the contrast in the darker regions of the image.
对数拉伸:对帧中的每个像素进行对数操作。

One of these three mathematical transformations is applied to every frame of the video. After it passes through the Sample Grabber, the video is DV (digital video) encoded and rendered to an output
file. The resulting AVI file can be viewed with Windows Media Player. (The algorithms implemented in these histogram operations were found in Handbook of Image and Video Processing, Al Bovik, ed., Academic Press, 2000.)
上述的三个数学转换之一会被应用到视频的每一帧。视频经过Sample Grabber后,这个视频被编码并渲染到输出文件。AVI文件可以通过Windows Media Player来播放。

Creating a Filter Graph Containing a Sample Grabber
The Histogram is a console-based application that has hard-coded file names inside it, and it produces an output AVI file at C:\Histogram_Output.AVI. (You’ll need to have Sample.AVI in the root of
drive C.) The application’s simple filter graph is built, run, and torn down in one function, RunFile:
柱状图是一个基于控制台的应用程序,在它内部有一个固定编码文件名:C:\Histogram_Output.AVI
应用程序的简单的filter graph的创建,运行,和关闭都一个函数RunFile中:

HRESULT RunFile(LPTSTR pszSrcFile, LPTSTR pszDestFile)

{

HRESULT hr;

USES_CONVERSION; // For TCHAR -> WCHAR
conversion macros

CComPtr<IGraphBuilder> pGraph; // Filter Graph
Manager

CComPtr<IBaseFilter> pGrabF; // Sample grabber

CComPtr<IBaseFilter> pMux; // AVI Mux

CComPtr<IBaseFilter> pSrc; // Source filter

CComPtr<IBaseFilter> pDVEnc; // DV Encoder

CComPtr<ICaptureGraphBuilder2> pBuild;

// Create the Filter Graph Manager.

hr = pGraph.CoCreateInstance(CLSID_FilterGraph);

if (FAILED(hr))

{

printf("Could not create the Filter Graph Manager (hr = 0x%X.)\n",

hr);

return hr;

}

// Create the Capture Graph Builder.

hr = pBuild.CoCreateInstance(CLSID_CaptureGraphBuilder2);

if (FAILED(hr))

{

printf("Could not create the Capture Graph Builder (hr = 0x%X.)\n",

hr);

return hr;

}

pBuild->SetFiltergraph(pGraph);

// Build the file-writing portion of the graph.

hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Avi, T2W(pszDestFile),

if (FAILED(hr))

{

printf("Could not hook up the AVI Mux / File Writer (hr = 0x%X.)\n",

hr);

return hr;

}

// Add the source filter for the input file.

hr = pGraph->AddSourceFilter(T2W(pszSrcFile), L"Source", &pSrc);

if (FAILED(hr))

{

printf("Could not add the source filter (hr = 0x%X.)\n", hr);

return hr;

}

// Create some filters and add them to the graph.

// DV Video Encoder

hr = AddFilterByCLSID(pGraph, CLSID_DVVideoEnc, L"DV Encoder", &pDVEnc);

if (FAILED(hr))

{

printf("Could not add the DV video encoder filter (hr = 0x%X.)\n",

hr);

return hr;

}

Although the Histogram source code uses the Active Template Library (ATL) features of Visual C++,
this code should be very familiar by now. A Filter Graph Manager and a Capture Graph Builder are
both instantiated and appropriately initialized. The Capture Graph Builder method SetFileName
establishes the file name for the output AVI file, and AddSourceFilter is used to add the source file.
Next a DV Encoder filter (the output stream is DV-encoded) is added. At this point, the Sample
Grabber filter is instanced, added to the filter graph, and initialized before it’s connected to any other
filters.
尽管柱状图的源代码使用了Visual C++的ATL功能,但是它的代码还是和前面的有很多相像的。首先,Filter Graph Manager和Capture Graph Builde被实例化后初始化。Capture Graph Builder的方法SetFileName创建输出AVI文件的文件名,然后使用AddSourceFilter来添加源文件。接着添加DV编码filter。此时,实例化Sample Grabber filter并将其添加到filter
graph, 并在连接其它的filter之前初始化。


// Sample Grabber.

hr = AddFilterByCLSID(pGraph, CLSID_SampleGrabber, L"Grabber", &pGrabF);

if (FAILED(hr))

{

printf("Could not add the sample grabber filter (hr = 0x%X.)\n",

hr);

return hr;

}

CComQIPtr<ISampleGrabber> pGrabber(pGrabF);

if (!pGrabF)

{

return E_NOINTERFACE;

}

// Configure the Sample Grabber.

AM_MEDIA_TYPE mt;

ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));

mt.majortype = MEDIATYPE_Video;

mt.subtype = MEDIASUBTYPE_UYVY;

mt.formattype = FORMAT_VideoInfo;

// Note: I don't expect the next few methods to fail...

hr = pGrabber->SetMediaType(&mt); // Set the
media type

_ASSERTE(SUCCEEDED(hr));

hr = pGrabber->SetOneShot(FALSE); // Disable "one-shot" mode

_ASSERTE(SUCCEEDED(hr));

hr = pGrabber->SetBufferSamples(FALSE); // Disable
sample buffering

_ASSERTE(SUCCEEDED(hr));

hr = pGrabber->SetCallback(&grabberCallback, 0); // Set our
callback

// '0' means 'use the SampleCB callback'

_ASSERTE(SUCCEEDED(hr));

Using the ATL smart pointers, a QueryInterface call is made to the Sample Grabber filter object
requesting its ISampleGrabber interface. Once that interface has been acquired, the Sample Grabber
can be configured. First the Sample Grabber has to be given a media type, so an AM_MEDIA_TYPE
structure is instantiated and filled with a major type of MEDIATYPE_Video and a subtype of
MEDIASUBTYPE_UYVY. (As covered in Chapter 10, this subtype means that the media samples in
the stream are in YUV format, specifically UYVY.) A call to the ISampleGrabber method
SetMediaType establishes the media type for the Sample Grabber. The Sample Grabber will accept
any media type until a call is made to SetMediaType, at which point it will accept only matching media
types. It’s vital that the media type be set before the Sample Grabber is connected to other filters so
that they can negotiate to meet the needs of the Sample Grabber.
使用ATL指针,Sample Grabber filter对象会调用 QueryInterface来请求它的ISampleGrabber接口。当获得了这个接口后,就可以进行Sample Grabber的配置。首先,必须指定Sample Grabber的媒体类型,因此,AM_MEDIA_TYPE结构体会被实例化并对主类型赋值MEDIATYPE_Video,子类型赋值为MEDIASUBTYPE_UYVY。(如第10章所示,子类型表示流中的媒体样本为YUV格式,具体为UYVY)。接着调用ISampleGrabber的SetMediaType方法建立Sample
Grabber的媒体类型。Sample Grabber将会接受所有的媒体类型直到调用了SetMediaType,这时就只会接受SetMediaType中匹配的类型。这个动作在将Sample Grabber和其它filter连接起来之前是很重要的,只有这样才能使其它 的filter和Sample Grabber连接时进行协商。


A bit more initialization needs to be done to ensure the proper operation of the Sample Grabber.
The
Sample Grabber has the option of stopping the execution of the filter graph after it receives its first
media sample, which comes in handy when grabbing still frames from a video file but would be a
detriment to the operation of the histogram. To prevent this one-shot behavior from happening (we
want continuous operation of the filter graph), we pass FALSE to the ISampleGrabber method
SetOneShot. The Sample Grabber can also buffer samples internally as they pass through the filter;
buffering is disabled by passing FALSE to SetBufferSamples. Finally the callback function within the
Histogram application is initialized with a call to SetCallback. The Sample Grabber will call this function
every time it receives a media sample. (The format of the object passed to SetCallback is discussed in
the next section.) With the Sample Grabber filter fully initialized, construction of the filter graph can
continue.
Sample Grabber还要进行一些初始化。Sample Grabber有在它收到第一个媒体样本后停止filter graph执行的选项,当需要从文件中抓取静止帧时,这个功能很有用。但是在现在的这个例子中将会破坏柱状图应用的操作。为了防止快照行为(因为本例需要的是filter graph的连续执行),将ISampleGrabber的SetOneShot方法传递FALSE参数。Sample
Grabber在样本经过filter进同样也能在内部进行样本缓存;要关闭缓存可能可以通过SetBufferSamples传FALSE参数。最后,柱状图应用程序通过调用SetCallback来初始化回调函数,这样,每次接收到一个样本后Sample Grabber就会调用这个函数。(传递给SetCallback的对象的格式将在下章中讨论)。当Sample Grabber初始化完后,就可以继续filter graph的构建。


点击(此处)折叠或打开

// Build the graph.

// First connect the source to the DV Encoder,

// through the Sample Grabber.

// This should connect the video stream.

hr = pBuild->RenderStream(0, 0, pSrc, pGrabF, pDVEnc);

if (SUCCEEDED(hr))

{

// Next, connect the DV Encoder to the AVI Mux.

hr = pBuild->RenderStream(0, 0, pDVEnc, 0, pMux);

if (SUCCEEDED(hr))

{

// Maybe we have an audio stream.

// If so, connect it the AVI Mux.

// But don't fail if we don't...

HRESULT hrTmp = pBuild->RenderStream(0, 0, pSrc, 0, pMux);

SaveGraphFile(pGraph, L"C:\\Grabber.grf");

}

}

if (FAILED(hr))

{

printf("Error building the graph (hr = 0x%X.)\n", hr);

return hr;

}

// Find out the exact video format.

hr = pGrabber->GetConnectedMediaType(&mt);

if (FAILED(hr))

{

printf("Could not get the video format. (hr = 0x%X.)\n", hr);

return hr;

}

VIDEOINFOHEADER *pVih;

if ((mt.subtype == MEDIASUBTYPE_UYVY) &&

(mt.formattype == FORMAT_VideoInfo))

{

pVih = reinterpret_cast<VIDEOINFOHEADER*>(mt.pbFormat);

}

else

{

// This is not the format we

CoTaskMemFree(mt.pbFormat);

return VFW_E_INVALIDMEDIATYPE;

}

Once the graph has been connected together with a few calls to RenderStream, the Sample Grabber
is queried for its media type using the ISampleGrabber method GetConnectedMediaType. If the
returned subtype isn’t MEDIASUBTYPE_UYVY, the function returns an error because the Histogram
application can’t process video frames in any format except UYVY. The function also checks that the
format type is FORMAT_VideoInfo, which defines the format structure as a VIDEOINFOHEADER
type. This check is performed because the Histogram application wasn’t written to handle
VIDEOINFOHEADER2 format types. (The Sample Grabber filter doesn’t accept
VIDEOINFOHEADER2 formats either.) The VIDEOINFOHEADER2 structure is similar to
VIDEOINFOHEADER, but it adds support for interlaced fields and image aspect ratios.
当graph和RenderStream连接后,Sample Grabber会通过ISampleGrabber的方法GetConnectedMediaType来请求要处理媒体类型。如是返回的子类型不是MEDIASUBTYPE_UYVY,则会因为状态图应用程序不能处理除UYVY外的视频帧而返回错误。这个函数也会检查格式类型FORMAT_VideoInfo,这是以VIDEOINFOHEADER定义的结构体。这个检查的原因是柱状图应用程序不能处理VIDEOINFOHEADER2格式(Sample
Grabber filter也不能接受VIDEOINFOHEADER2这个格式). VIDEOINFOHEADER2和VIDEOINFOHEADER很相似,但是它添加了对场和宽高比的支持。


点击(此处)折叠或打开

g_stretch.SetFormat(*pVih);

CoTaskMemFree(mt.pbFormat);

// Turn off the graph clock.

CComQIPtr<IMediaFilter> pMF(pGraph);

pMF->SetSyncSource(NULL);

// Run the graph to completion.

CComQIPtr<IMediaControl> pControl(pGraph);

CComQIPtr<IMediaEvent> pEvent(pGraph);

long evCode = 0;

printf("Processing the video file... ");

pControl->Run();

pEvent->WaitForCompletion(INFINITE, &evCode);

pControl->Stop();

printf("Done.\n");

return hr;

}

A global object, g_stretch, implements the methods that perform the mathematical transformation on
the media sample. (One of three different instances of g_stretch can be created, depending on which
line of code is uncommented. These three instances correspond to the three types of transforms
implemented in the Histogram application.) A call to the g_stretch SetFormat method allows it to
initialize with the appropriate height, width, and bit depth information needed for successful operation.
全局的对象,g_stretch,实现了媒体样本的数学转换功能。(柱状图的三种功能可选)。g_stretch的SetFormat方法可以用来初始化高,宽和比特位深度。

In a final step, a call is made to the IMediaFilter interface method SetSyncSource. This call sets
the
reference clock for the filter graph, which is used to preserve synchronization among all graph
components and to define the presentation time for the filter graph. When passed NULL, as it is here,
SetSyncSource turns off the filter graph’s reference clock, allowing the filter graph components to run
at their own rates, which is desirable because you will want the filter graph to process each sample as
quickly as possible. If a reference clock was active, some filter graph components might choose to
slow the graph down to maintain synchronization across the filter graph. With the reference clock
turned off, this won’t happen. (There aren’t any filters in this particular filter graph that keep track of the
reference clock, so we’re being a bit overzealous here, for the sake of the example.)
最后,调用IMediaFilter接口的SetSyncSource方法。这个调用是用来设置filter graph的参考时钟,它用来保持所有graph组件的同步并定义filter graph的表示时间。当传NULL参数时,表示SetSyncSource关闭filter
graph的参考时钟,允许各个组件以自己的速率运行,这是本例不期望的,因为想要的是filter graph处理每个样本尽可能的快。如果激活了参考时钟,某些filter graph组件将会选择慢于同步。如果关闭参考时钟则不会发生这样的问题。


Now that everything has been initialized, the IMediaControl and IMediaEvent interfaces are acquired
from the Filter Graph Manager. Finally the filter graph is run to completion.
到目前为止,就完成了所有的初始化,并请求Filter Graph Manager得到了IMediaControl和IMediaEvent接口。最后,运行filter graph直到完成。

Defining the Sample Grabber Callback Object
The Sample Grabber was initialized with a callback object, which acts as a hook between the Sample
Grabber and the Histogram application. Although instantiated by the Histogram application, the
Sample Grabber doesn’t know anything about the application; specifically, the Sample Grabber
doesn’t know how to pass media samples to the application. The interface between the application
and the Sample Grabber is managed with the callback object GrabberCB, which is defined and
implemented as follows:
Sample Grabber是通过回调对象初始化的,这个回调对象以钩子的方式在Sample Grabber和应用程序间工作。尽管Sample Grabber是被应用程序实例化的,但是它并不知道应用程序的任何事情;特别地,它也不知道应用程序是如何传输媒体样本的。应用程序和Sample Grabber之间的接口由回调对象GrabberCB管理,它的定义和实现如下:

点击(此处)折叠或打开

class GrabberCB : public ISampleGrabberCB

{

private:

BITMAPINFOHEADER m_bmi; // Holds the bitmap format

bool m_fFirstSample; // True if the next sample is the
first one

public:

GrabberCB();

~GrabberCB();

// IUnknown methods

STDMETHODIMP_(ULONG) AddRef() { return 1; }

STDMETHODIMP_(ULONG) Release() { return 2; }

STDMETHOD(QueryInterface)(REFIID iid, void** ppv);

// ISampleGrabberCB methods

STDMETHOD(SampleCB)(double SampleTime, IMediaSample *pSample);

STDMETHODIMP BufferCB(double, BYTE *, long) { return
E_NOTIMPL; }

};

GrabberCB::GrabberCB() : m_fFirstSample(true)

{

}

GrabberCB::~GrabberCB()

{

}

// Support querying for ISampleGrabberCB interface

HRESULT GrabberCB::QueryInterface(REFIID iid, void**ppv)

{

if (!ppv) { return E_POINTER; }

if (iid == IID_IUnknown)

{

*ppv = static_cast<IUnknown*>(this);

}

else if (iid == IID_ISampleGrabberCB)

{

*ppv = static_cast<ISampleGrabberCB*>(this);

}

else

{

return E_NOINTERFACE;

}

AddRef(); // We don't
actually ref count,

// but in case we change the implementation later.

return S_OK;

}

// SampleCB: This is where we process each sample.

HRESULT GrabberCB::SampleCB(double SampleTime, IMediaSample *pSample)

{

HRESULT hr;

// Get the pointer to the buffer.

BYTE *pBuffer;

hr = pSample->GetPointer(&pBuffer);

// Tell the image processing class about it.

g_stretch.SetImage(pBuffer);

if (FAILED(hr))

{

OutputDebugString(TEXT("SampleCB: GetPointer FAILED\n"));

return hr;

}

// Scan the image on the first sample.

// Re-scan if there is a discontinuity.

// (This will produce horrible results

// if there are big scene changes in the

// video that are not associated with discontinuities.

// Might be safer to re-scan

// each image, at a higher perf cost.)

if (m_fFirstSample)

{

hr = g_stretch.ScanImage();

m_fFirstSample = false;

}

else if (S_OK == pSample->IsDiscontinuity())

{

hr = g_stretch.ScanImage();

}

// Convert the image.

hr = g_stretch.ConvertImage();

return S_OK;

}

The class declaration for GrabberCB inherits the methods of the ISampleGrabberCB interface. This
interface defines two methods that must be implemented by the callback object, GrabberCB::BufferCB
and GrabberCB:: SampleCB. However, the Sample Grabber filter can use only one of these callback
methods at a time, so you can simply return E_NOTIMPL from the one you aren’t going to use. The
GrabberCB::BufferCB method would receive a sample buffer from the Sample Grabber, but because
sample buffering was disabled when the Sample Grabber was instantiated, this method simply returns
the error code E_NOTIMPL. The GrabberCB::QueryInterface implementation ensures that the
appropriate interface is returned when handling QueryInterface calls made on the GrabberCB object.
类GrabberCB继承自ISampleGrabberCB接口。这个接口定义了两个必须由回调对象实现的方法,GrabberCB::BufferCB和GrabberCB::SampleCB。但是,Sample Grabber filter每次只会调用其中的一个回调方法,因此,只要在不用的调用中返回E_NOTIMPL即可。
GrabberCB::BufferCB方法: 可以从Sample Grabber中接收一个样本缓冲区,但是,在实例化时是关闭了
Sample Grabber的样本缓存的,因此,这个方法返回错误代码E_NOTIMPL。
GrabberCB::QueryInterface:确保返回合适的接口。

The GrabberCB::AddRef and GrabberCB::Release methods implement fake reference counting.
Normally, COM requires that an object keep a reference count and delete itself when the reference
count goes to zero. In this case, we don’t keep a reference count of this object, which means that the
callback object must have a wider scope than the DirectShow filter graph so that the object doesn’t
accidentally get deleted while the Sample Grabber is still using it. That is why the grabberCallback
object is implemented as a global variable. The variable stays in scope for the duration of the
Histogram application’s execution, and the object is automatically deleted when the application
terminates execution.
GrabberCB::AddRef和GrabberCB::Release实现了假的引用计数。通常,COM要求对象保存引用计数并在引用计数为零时删除对象自身。在本例中,并不需要保留对象的引用计数,这意味着回调对象比DS filter有更大的范围,从而不会导致Sample Grabber还在使用中这个对象却被删除了。这也就是为什么grabberCallback对象要以全局变量实现。这个变量在应用程序执行期间内都在作用范围之内,当应用程序结束时会自动删除。

Now we come to the heart of the GrabberCB object, the implementation of GrabberCB::SampleCB.
This method is called every time the Sample Grabber filter has a sample to present to the Histogram
application. The method communicates with g_stretch, the object that manages the histogram
transformation, passing it a pointer to the media sample’s memory buffer, which contains the raw
sample data. On the first time through the method (that is, when the first media sample is ready for
processing by the Histogram application), the m_fFirstSample flag is set true and the method calls
ScanImage, which allows the histogram object to learn the particulars of the video frames that it will be
processing. (This also happens if a discontinuity is detected, which happens when the filter graph
pauses and restarts.) Finally the method calls the ConvertImage method of the histogram object,
which performs the in-place transformation of the data in the buffer.
接着来看GrabberCB::SampleCB的实现,每当Sample Grabber filter有一个样本输出给应用程序时就会调用这个方法。这个方法和g_stretch通信,并传给它一个媒体样本的buffer指针(这个指针包含有原始样本数据)。当第一次调用这个方法时,m_fFirstSample标志置为真并调用ScanImage方法,它允许柱状图对象得到视频帧的细节信息。最后,这个方法调用柱状图对象的ConvertImage方法来执行buffer中的本地数据转换。

Although this might seem like a minimal implementation, this is all the implementation that’s required
to add the Sample Grabber filter to your DirectShow applications. The interesting part is what your
application does with the media samples delivered to it by the Sample Grabber.
尽管这是一很简单的实现,但是它是所有在DS应用程序添加Sample Grabber filter必要的。有兴趣的部分在应用程序如何处理从Sample Grabber得到的媒体样本。

Processing Sample Grabber Media Samples
There are only a few major restrictions on the kind of processing that can take place on a sample
presented by the Sample Grabber. The transformations must be performed in-place, within the same
buffer presented by the Sample Grabber to the application. So, media format translations are not
permissible, nor is any other operation that affects the media type or buffer size of the sample. If the
Sample Grabber is upstream of a video renderer, performance of the filter graph will suffer because it
will probably require hardware reads of the video graphics card, which can slow processing
dramatically, as explained in Chapter 10.
对从Sample Grabber得到的样本的处理限制很少。因为转换是在当前buffer中执行的,因此媒体格式转换是不允许的,其它的改变媒体类型的转换也是不允许的。如果Sample Grabber是将样本提交给视频渲染,filter graph的性能就会很重要。

In the Histogram application, the histogram image processing object CImagePointOp uses three
methods for data translation: SetFormat, ScanImage, and ConvertImage. Because there are three
possible implementations of ScanImage and ConvertImage, depending on which global histogram
object is uncommented in the source code, we present the default case, CEqualize (which brings out
detail across the image) as if it were the only implementation.
在柱状图应用程序中,柱状图图像处理对象CImagePointOp 使用三个数据转换方式:SetFormat, ScanImage, 和ConvertImage。因为ScanImage 和ConvertImage有三种可能的实现方式,这里使用默认的方式CEqualize作为其唯一实现:

点击(此处)折叠或打开

HRESULT CImageOp::SetFormat(const VIDEOINFOHEADER& vih)

{

// Check if UYVY.

if (vih.bmiHeader.biCompression != 'YVYU')

{

return E_INVALIDARG;

}

int BytesPerPixel = vih.bmiHeader.biBitCount / 8;

// If the target rectangle (rcTarget) is empty,

// the image width and the stride are both biWidth.

// Otherwise, image width is given by rcTarget

// and the stride is given by biWidth.

if (IsRectEmpty(&vih.rcTarget))

{

m_dwWidth = vih.bmiHeader.biWidth;

m_lStride = m_dwWidth;
}

else

{

m_dwWidth = vih.rcTarget.right;

m_lStride = vih.bmiHeader.biWidth;

}

// Stride for UYVY is rounded to the nearest DWORD.

m_lStride = (m_lStride * BytesPerPixel + 3) & ~3;

// biHeight can be < 0, but YUV is always top-down.

m_dwHeight = abs(vih.bmiHeader.biHeight);

m_iBitDepth = vih.bmiHeader.biBitCount;

return S_OK;

}

HRESULT CEqualize::_ScanImage()

{

DWORD iRow, iPixel; // looping variables

BYTE *pRow = m_pImg; // pointer to the
first row in the buffer

DWORD histogram[LUMA_RANGE]; // basic histogram

double nrm_histogram[LUMA_RANGE]; // normalized histogram

ZeroMemory(histogram, sizeof(histogram));

// Create a histogram.

// For each pixel, find the luma and increment
the count for that

// pixel. Luma values are translated

// from the nominal 16-235 range to a 0-219 array.

for (iRow = 0; iRow < m_dwHeight; iRow++)

{

UYVY_PIXEL *pPixel = reinterpret_cast<UYVY_PIXEL*>(pRow);

for (iPixel = 0; iPixel < m_dwWidth; iPixel++, pPixel++)

{

BYTE luma = pPixel->y;

luma = static_cast<BYTE>(clipYUV(luma)) - MIN_LUMA;

histogram[luma]++;

}

pRow += m_lStride;

}

// Build the cumulative histogram.

for (int i = 1; i < LUMA_RANGE; i++)

{

// The i'th entry is the sum of the previous entries.

histogram[i] = histogram[i-1] + histogram[i];

}

// Normalize the histogram.

DWORD area = NumPixels();

for (int i = 0; i < LUMA_RANGE; i++)

{

nrm_histogram[i] =

static_cast<double>( LUMA_RANGE * histogram[i] ) / area;

}

// Create the LUT.

for (int i = 0; i < LUMA_RANGE; i++)

{

// Clip the result to the nominal luma range.

long rounded = static_cast<long>(nrm_histogram[i] + 0.5);

long clipped = clip(rounded, 0, LUMA_RANGE - 1);

m_LookUpTable[i] = static_cast<BYTE>(clipped) + MIN_LUMA;

}

return S_OK;

}

HRESULT CImagePointOp::_ConvertImage()

{

DWORD iRow, iPixel; // looping variables

BYTE *pRow = m_pImg; // pointer to the
first row in the buffer

for (iRow = 0; iRow < m_dwHeight; iRow++)

{

UYVY_PIXEL *pPixel = reinterpret_cast<UYVY_PIXEL*>(pRow);

for (iPixel = 0; iPixel < m_dwWidth; iPixel++, pPixel++)

{

// Translate luma back to 0-219 range.

BYTE luma = (BYTE)clipYUV(pPixel->y) - MIN_LUMA;

// Convert from LUT.

// The result is already in the correct 16-239
range.

pPixel->y = m_LookUpTable[luma];

}
pRow += m_lStride;

}

return S_OK;

}

The first method, CImageOp::SetFormat, is called from RunFile when all the connections have been
made across the filter graph and the Sample Grabber is fully aware of the media type it will be
presenting to the histogram object. (Although the Sample Grabber is initialized with a media type and
subtype, the histogram object needs more detailed information to process each frame of video.) From
the passed VIDEOINFOHEADER parameter, the method learns the width, height, stride, and bit depth
of the image, information that the histogram object will need for processing.
当filter graph的所有连接完成时RunFile就会调用方法CImageOp::SetFormat,此时,Sample Grabber将会知道柱状图对象的媒体类型(尽管Sample Grabber 已初始化了媒体类型和子类型,但是柱状图对象为了处理每个视频帧还需要更多的信息)。从VIDEOINFOHEADER参数,这个方法可以获得柱状图对象将要处理的视频帧的宽,高,stride和位深等信息。

When the Sample Grabber receives its first media sample from an upstream filter, it calls
CEqualize::ScanImage from GrabberCB::SampleCB. This method walks through the image, pixel by
pixel, creating a histogram, from which it extracts maximum and minimum luma values. (Luma is
covered in the sidebar on YUV formats in Chapter 10.) This histogram is then normalized, and these
values are placed into an array that is used by CImageOp::ConvertImage. When
CImageOp::ConvertImage is called, the method extracts the old luma value of a pixel, looks it up in the
array created by CEqualize::ScanImage, and inserts a new luma value for the pixel.
当Sample Grabber从上游filter收到第一个媒体样本后,GrabberCB::SampleCB就会调用CEqualize::ScanImage。这个方法通过图像,逐像素地创建柱状图,它提取了亮度的最大和最小值。然后柱状图被归一化,并将这些值保存到数组中,交给CImageOp::ConvertImage使得。当CImageOp::ConvertImage被调用时,这个方法抽取旧的像素值,查找数据并插入新的像素值。

The differences between the three histogram techniques offered by this application are entirely based
in the three implementations of the ScanImage method. Each method generates a unique array of
luma values, which thereby changes the output of CImageOp::ConvertImage. You could easily write
your own ScanImage implementations, adapting the code in this example program, to produce unique
video effects.
三种不同的柱状图实现是由ScanImage的不同实现来做到

This concludes the exploration of the Sample Grabber from the application designer’s point of view.
Now we’ll examine the source code of the Grabber Sample, which is an alternate implementation of
the Sample Grabber, with its own unique class ID and interface ID. The Grabber Sample source code
is included in the DirectX SDK. It has a unique design that illuminates many internal features of
DirectShow.
从应用程序员的角度来看,Grabber Sample的代码已完成,下面是Grabber Sample的一个相对实现。

Exploring the Grabber Sample Source Code
The Grabber Sample begins with a class ID and an interface ID unique to the module, so it can be instantiated (with its class ID) and queried for its interfaces (with its interface ID). Next is a typedef
that defines the callback format—that is, what data will be passed to the application through the Grabber Sample. This callback format is different from the one used by the Sample Grabber. The Grabber Sample callback is implemented as a function,
not as a COM interface as it is in the Sample Grabber. Next comes the interface definition for the IGrabberSample interface. IGrabberSample implements methods that the application uses to communicate with the Grabber Sample. This interface can be located using
QueryInterface.

点击(此处)折叠或打开

//-------------------------------------------------------------------------

// Define new GUID and IID for the grabber example so that they do NOT

// conflict with the official DirectX Grabber Sample filter

//-------------------------------------------------------------------------

// {2FA4F053-6D60-4cb0-9503-8E89234F3F73}

DEFINE_GUID(CLSID_GrabberSample,

0x2fa4f053, 0x6d60, 0x4cb0, 0x95, 0x3, 0x8e, 0x89, 0x23, 0x4f, 0x3f, 0x73);

DEFINE_GUID(IID_IGrabberSample,

0x6b652fff, 0x11fe, 0x4fce, 0x92, 0xad, 0x02, 0x66, 0xb5, 0xd7, 0xc7, 0x8f);

// We define a callback typedef for this example.

// Normally, you would make the Grabber Sample support a COM interface,

// and in one of its methods you would pass in a pointer to a
COM interface

// used for calling back.

typedef HRESULT (*SAMPLECALLBACK) (

IMediaSample * pSample,

REFERENCE_TIME * StartTime,

REFERENCE_TIME * StopTime,

BOOL TypeChanged );

// We define the interface the app can use to program us

MIDL_INTERFACE("6B652FFF-11FE-4FCE-92AD-0266B5D7C78F")

IGrabberSample : public IUnknown

{

public:

virtual HRESULT STDMETHODCALLTYPE SetAcceptedMediaType(

const CMediaType *pType) = 0;

virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType(

CMediaType *pType) = 0;

virtual HRESULT STDMETHODCALLTYPE SetCallback(

SAMPLECALLBACK Callback) = 0;

virtual HRESULT STDMETHODCALLTYPE SetDeliveryBuffer(

ALLOCATOR_PROPERTIES props,

BYTE *pBuffer) = 0;

};

Defining and Implementing the Filter Class
The definition of the filter class CSampleGrabber is closely based on the definition of the IGrabberSample interface. CSampleGrabber includes declarations for all the methods declared within IGrabberSample,
but in this case, it actually includes the implementations of these methods. Here’s the definition of the CSampleGrabber class:

点击(此处)折叠或打开

class CSampleGrabber : public CTransInPlaceFilter,

public IGrabberSample

{

friend class CSampleGrabberInPin;

friend class CSampleGrabberAllocator;

protected:

CMediaType m_mtAccept;

SAMPLECALLBACK m_callback;

CCritSec m_Lock; // serialize access to our data

BOOL IsReadOnly( ) { return !m_bModifiesData; }

// PURE, override this to ensure we get

// connected with the right media type

HRESULT CheckInputType( const CMediaType * pmt );

// PURE, override this to callback

// the user when a sample is received

HRESULT Transform( IMediaSample * pms );

// override this so we can return S_FALSE directly.

// The base class CTransInPlace

// Transform( ) method is called by its

// Receive( ) method. There is no
way

// to get Transform( ) to return
an S_FALSE value

// (which means "stop giving me data"),

// to Receive( ) and get Receive( ) to return
S_FALSE as well.

HRESULT Receive( IMediaSample * pms );

public:

static CUnknown *WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);

// Expose IGrabberSample

STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

DECLARE_IUNKNOWN;

CSampleGrabber( IUnknown * pOuter, HRESULT * pHr, BOOL
ModifiesData );

// IGrabberSample

STDMETHODIMP SetAcceptedMediaType( const CMediaType * pmt );

STDMETHODIMP GetConnectedMediaType( CMediaType * pmt );

STDMETHODIMP SetCallback( SAMPLECALLBACK Callback );

STDMETHODIMP SetDeliveryBuffer( ALLOCATOR_PROPERTIES props,

BYTE * m_pBuffer );

};

CSampleGrabber uses the C++ feature of multiple inheritance, declaring both CTransInPlaceFilter and
ISampleGrabber as ancestor classes and inheriting the variables and implementation of both classes. The only
method implemented in the class definition is the IsReadOnly method, which checks the m_bModifiesData Boolean variable and returns its inverse. If m_bModifiesData is true, the filter is modifying data in
its buffers.

Here’s the implementation of the three protected methods of CSampleGrabber—CheckInputType, Transform, and Receive:

点击(此处)折叠或打开

HRESULT CSampleGrabber::CheckInputType( const CMediaType * pmt )

{

CheckPointer(pmt,E_POINTER);

CAutoLock lock( &m_Lock );

// if the major type is not set, then accept
anything

GUID g = *m_mtAccept.Type( );

if( g == GUID_NULL )

{

return NOERROR;

}

// if the major type is set, don't
accept anything else

if( g != *pmt->Type( ) )

{

return VFW_E_INVALID_MEDIA_TYPE;

}

// subtypes must match, if set. if not set, accept
anything

g = *m_mtAccept.Subtype( );

if( g == GUID_NULL )

{

return NOERROR;

}

if( g != *pmt->Subtype( ) )

{

return VFW_E_INVALID_MEDIA_TYPE;

}

// format types must match, if one is set

g = *m_mtAccept.FormatType( );

if( g == GUID_NULL )

{

return NOERROR;

}

if( g != *pmt->FormatType( ) )

{

return VFW_E_INVALID_MEDIA_TYPE;

}

// at this point, for this sample code, this is good
enough,

// but you may want to make it more strict

return NOERROR;

}

HRESULT CSampleGrabber::Receive( IMediaSample * pms )

{

CheckPointer(pms,E_POINTER);

HRESULT hr;

AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps();

if (pProps->dwStreamId != AM_STREAM_MEDIA)

{

if( m_pOutput->IsConnected() )

return m_pOutput->Deliver(pms);

else

return NOERROR;

}

if (UsingDifferentAllocators())

{

// We have to copy the data.

pms = Copy(pms);

if (pms == NULL)

{

return E_UNEXPECTED;

}

}

// have the derived class transform the data

hr = Transform(pms);

if (FAILED(hr))

{

DbgLog((LOG_TRACE, 1, TEXT("Error
from TransInPlace")));

if (UsingDifferentAllocators())

{

pms->Release();

}

return hr;

}

if (hr == NOERROR)

{

hr = m_pOutput->Deliver(pms);

This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
it. Thanks

}

// release the output buffer. If the connected pin still needs it,

// it will have addrefed it itself.

if (UsingDifferentAllocators())

{

pms->Release();

}

return hr;

}

HRESULT CSampleGrabber::Transform ( IMediaSample * pms )

{

CheckPointer(pms,E_POINTER);

CAutoLock lock( &m_Lock );

if( m_callback )

{

REFERENCE_TIME StartTime, StopTime;

pms->GetTime( &StartTime, &StopTime);

StartTime += m_pInput->CurrentStartTime( );

StopTime += m_pInput->CurrentStartTime( );

BOOL * pTypeChanged =

&((CSampleGrabberInPin*) m_pInput)->m_bMediaTypeChanged;

HRESULT hr =

m_callback( pms, &StartTime, &StopTime, *pTypeChanged );

*pTypeChanged = FALSE; // now that
we notified user, can clear it

return hr;

}

return NOERROR;

}

The CheckInputType method implements a series of comparisons against the media type that was specified by the application instancing the Grabber Sample. If no local type has been set for the CSampleGrabber
object—implying that the application did not call SetAcceptedMediaType when initializing the filter—anything passed to CheckInputType will return without an error. If a media type is defined, the major type must match, and the subtype and
format must match if they’ve been defined. Like the Sample Grabber, the Grabber Sample checks only the major type, subtype, and format type. It doesn’t examine the details of the format (for example, sample rate on an audio stream, or image width and height
in a video stream), so it’s possible that the filter will accept a connection to a media stream that the application can’t work with.

The Receive method is, in many ways, the heart of the filter class implementation. It overrides
CTransInPlaceFilter::Receive, which is called by the input pin’s IMemInputPin::Receive method. The method
examines the properties of the sample that has been presented on the input pin by calling the
CBaseInputPin::SampleProps method, which returns an AM_SAMPLE2_PROPERTIES data structure. The method then examines the dwStreamId field of the structure. If this field contains the value AM_STREAM_MEDIA,
the sample presented on the input pin is stream data and needs to be processed by the method. If the field contains anything else, the sample on the input pin contains stream control data and is immediately passed along to the Deliver method on the output
pin if the output pin is connected to anything downstream.

Next the method calls the CTransInPlaceFilter::UsingDifferentAllocators method. This method returns true if the input and output buffers are different. The Receive method then copies the buffers from the
input to output pin buffer using the CTransInPlaceFilter::Copy method, which is necessary because in some cases a transform-in-place filter is forced to act like a copy transform filter with two allocators. (This situation is described exhaustively in the
DirectX SDK documentation for the CTransInPlaceFilter class.) Once the buffer has been copied (if necessary), the class’s Transform method is invoked, which performs any necessary manipulations of the stream data as it passes from input to output
pin. If the Transform completes successfully, the buffer is sent along to the output pin with a call to the output pin’s Deliver method. The buffer allocated in the Copy method call (if any) is released, and the method exits.

The Transform method doesn’t do anything to the data in the buffer, so in this sense, this transform filter doesn’t transform anything. However, this method is where the call to the user-defined
callback is made. (This arrangement of filter and callback is similar to the relationship between the Histogram application and the Sample Grabber given earlier in this chapter.) If there’s a user-defined callback set up through an invocation of the SetCallback
method, it’s called. The callback is passed a pointer to the stream buffer, the sample’s start time and stop time (in REFERENCE_TIME units of 100 nanoseconds), and a flag indicating whether the media’s type has changed. (Your own filters should check for a
type change in either the Receive or the Transform method.) Once that’s done, Transform exits.

Now let’s look at the implementation of the class constructor and the methods that COM needs to publish through the IGrabberSample interface:

点击(此处)折叠或打开

CUnknown * WINAPI CSampleGrabber::CreateInstance(LPUNKNOWN punk, HRESULT *phr)

{

ASSERT(phr);

// assuming we don't want to modify the data

CSampleGrabber *pNewObject = new CSampleGrabber(punk, phr, FALSE);

if(pNewObject == NULL) {

if (phr)

*phr = E_OUTOFMEMORY;

}

return pNewObject;

}

CSampleGrabber::CSampleGrabber( IUnknown * pOuter, HRESULT * phr,

bool ModifiesData )

: CTransInPlaceFilter( TEXT("SampleGrabber"),

(IUnknown*) pOuter, CLSID_GrabberSample,

phr, (BOOL)ModifiesData )

, m_callback( NULL )

{

// this is used to override the input pin with our own

m_pInput = (CTransInPlaceInputPin*) new CSampleGrabberInPin( this, phr );

if( !m_pInput )

{

if (phr)

*phr = E_OUTOFMEMORY;

}

// Ensure that the output pin gets created.

// This is necessary because our

// SetDeliveryBuffer() method assumes

// that the input/output pins are created, but

// the output pin isn't created until GetPin() is called. The

// CTransInPlaceFilter::GetPin() method
will create the output pin,

// since we have not already created one.

IPin *pOutput = GetPin(1);

// The pointer is not AddRef'ed by GetPin(), so
don't release it

}

STDMETHODIMP CSampleGrabber::NonDelegatingQueryInterface( REFIID riid,

void ** ppv)

{

CheckPointer(ppv,E_POINTER);

if(riid == IID_IGrabberSample) {

return GetInterface((IGrabberSample *) this, ppv);

}

else {

return CTransInPlaceFilter::NonDelegatingQueryInterface(riid, ppv);

}

}

These methods translate neatly into COM methods that we’re already familiar with. The CreateInstance method is normally invoked by a COM call to CoCreateInstance, and it instantiates and then
returns a CSampleGrabber object. The CSampleGrabber::CSampleGrabber constructor method calls its parent’s constructor CTransInPlaceFilter::CTransInPlaceFilter for initialization. It then creates a new input pin with a call to the constructor for CSampleGrabberInPin
and an output pin with a call to the parent method CTransInPlaceFilter::GetPin. Finally the NonDelegatingQueryInterface method handles the particulars of the QueryInterface COM method and returns an IGrabberSample interface to a passed object, if that’s what’s
requested, or passes the request up the inheritance chain to CTransInPlaceFilter::NonDelegatingQueryInterface.

Now all that’s left are the few functions that implement the published interfaces on IGrabberSample:
SetAcceptedMediaType, GetConnectedMediaType, SetCallback, and SetDeliveryBuffer. Here’s the implementation of these four methods:

点击(此处)折叠或打开

STDMETHODIMP CSampleGrabber::SetAcceptedMediaType( const CMediaType * pmt )

{

CAutoLock lock( &m_Lock );

if( !pmt )

{

m_mtAccept = CMediaType( );

return NOERROR;

}

HRESULT hr;

hr = CopyMediaType( &m_mtAccept, pmt );

return hr;

}

STDMETHODIMP CSampleGrabber::GetConnectedMediaType( CMediaType * pmt )

{

if( !m_pInput || !m_pInput->IsConnected( ) )

{

return VFW_E_NOT_CONNECTED;

}

return m_pInput->ConnectionMediaType( pmt );

}

STDMETHODIMP CSampleGrabber::SetCallback( SAMPLECALLBACK Callback )

{

CAutoLock lock( &m_Lock );

m_callback = Callback;

return NOERROR;

}

STDMETHODIMP CSampleGrabber::SetDeliveryBuffer( ALLOCATOR_PROPERTIES props,

BYTE * m_pBuffer )

{

// have the input/output pins been created?

if( !InputPin( ) || !OutputPin( ) )

{

return E_POINTER;

}

// they can't be connected

// if we're going to be changing delivery buffers

if( InputPin( )->IsConnected( ) || OutputPin( )->IsConnected( ) )

{

return E_INVALIDARG;

}

return ((CSampleGrabberInPin*)m_pInput)->SetDeliveryBuffer( props,

m_pBuffer );

}

The SetAcceptedMediaType method establishes the media type acceptable to the filter. If NULL is passed, an empty CMediaType object is instantiated; otherwise, the object is copied locally using the DirectX
function CopyMediaType. The GetConnectedMediaType method will return the media type specified on the filter’s input pin if that pin is connected. SetCallback sets the member variable m_callback to the value of the supplied parameter. This value,
if it exists, will be used when the Transform method is invoked.

Finally the SetDeliveryBuffer method defines the allocator properties and buffer to be used by the filter. If either the input or output pin does not exist, the method fails, which is a defense against a
bad pointer and unlikely to happen. Alternatively, if both exist but either is connected, the function fails because if either of the pins is connected, it has already negotiated an allocator. If all these tests pass successfully, the actual work is passed
off to CSampleGrabberInPin::SetDeliveryBuffer.

Defining and Implementing the Allocator Class
Although the Grabber Sample is descended from CTransInPlaceFilter and doesn’t need to copy input or output buffers, it requires a custom allocator implementation because the Grabber Sample has
a feature that allows the application hooked into it to allocate the memory for the sample buffers. This feature gives the application even greater control over the Grabber Sample, as it creates and owns the memory pool that the Grabber Sample uses for its
IMediaSample objects. The application can set up this pool by calling CSampleGrabber::SetDeliveryBuffer. Here’s the implementation of the custom allocator methods:

点击(此处)折叠或打开

class CSampleGrabberAllocator : public CMemAllocator
{
friend class CSampleGrabberInPin;
friend class CSampleGrabber;
protected:
// our pin who created us
CSampleGrabberInPin * m_pPin;
public:
CSampleGrabberAllocator( CSampleGrabberInPin * pParent, HRESULT *phr )
: CMemAllocator( TEXT("SampleGrabberAllocator\0"), NULL, phr )
, m_pPin( pParent )
{
};
~CSampleGrabberAllocator( )
{
// wipe out m_pBuffer before we try to delete it.
// It's not an allocated buffer,
// and the default destructor will try to free it!
m_pBuffer = NULL;
}
HRESULT Alloc( );
void ReallyFree();
// Override to reject anything that does not match the actual buffer
// that was created by the application
STDMETHODIMP SetProperties(ALLOCATOR_PROPERTIES *pRequest,
ALLOCATOR_PROPERTIES *pActual);
};

The CSampleGrabberAllocator object is a descendant of CMemAllocator, which is a DirectX-defined object that allocates storage for media samples, such as DirectShow streams. This definition overrides the constructor
CSampleGrabberAllocator::CSampleGrabberAllocator, which does little more than invoke the parent method in CMemAllocator and the destructor CSampleGrabberAllocator::~CSampleGrabberAllocator, which sets the buffer pointer to NULL, ensuring that a buffer created
by the application is released by the application and not by the Grabber Sample. The real work takes place in the three CSampleGrabberAllocator methods: Alloc, ReallyFree, and SetProperties. Here’s the implementation of those methods:

点击(此处)折叠或打开

HRESULT CSampleGrabberAllocator::Alloc( )

{

// look at the base class code to see where this came

CAutoLock lck(this);

// Check he has called SetProperties

HRESULT hr = CBaseAllocator::Alloc();

if (FAILED(hr)) {

return hr;

}

// If the requirements haven't changed then don't
reallocate

if (hr == S_FALSE) {

ASSERT(m_pBuffer);

return NOERROR;

}

ASSERT(hr == S_OK); // we
use this fact in the loop below

// Free the old resources

if (m_pBuffer) {

ReallyFree();

}

// Compute the aligned size

LONG lAlignedSize = m_lSize + m_lPrefix;

if (m_lAlignment > 1)

{

LONG lRemainder = lAlignedSize % m_lAlignment;

if (lRemainder != 0)

{

lAlignedSize += (m_lAlignment - lRemainder);

}

}

ASSERT(lAlignedSize % m_lAlignment == 0);

// don't create the buffer - use what was passed to us

//

m_pBuffer = m_pPin->m_pBuffer;

if (m_pBuffer == NULL) {

return E_OUTOFMEMORY;

}

LPBYTE pNext = m_pBuffer;

CMediaSample *pSample;

ASSERT(m_lAllocated == 0);

// Create the new samples -

// we have allocated m_lSize bytes for each sample

// plus m_lPrefix bytes per sample as a prefix. We set the pointer to

// the memory after the prefix -

// so that GetPointer() will return a pointer to m_lSize
bytes.

for (; m_lAllocated < m_lCount; m_lAllocated++, pNext += lAlignedSize)

{

pSample = new CMediaSample(

NAME("Sample Grabber memory media sample"),

This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
it. Thanks

this,

&hr,

pNext + m_lPrefix, // GetPointer() value

m_lSize); // not including prefix

ASSERT(SUCCEEDED(hr));

if (pSample == NULL)

return E_OUTOFMEMORY;

// This CANNOT fail

m_lFree.Add(pSample);

}

m_bChanged = FALSE;

return NOERROR;

}

void CSampleGrabberAllocator::ReallyFree()

{

// look at the base class code to see where this came

// Should never be deleting this unless all buffers are freed

ASSERT(m_lAllocated == m_lFree.GetCount());

// Free up all the CMediaSamples

CMediaSample *pSample;

for (;;)

{

pSample = m_lFree.RemoveHead();

if (pSample != NULL)

{

delete pSample;

}

else

This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
it. Thanks

{

break;

}

}

m_lAllocated = 0;

// don't free the buffer - let the app do it

}

HRESULT CSampleGrabberAllocator::SetProperties(

ALLOCATOR_PROPERTIES *pRequest,

ALLOCATOR_PROPERTIES *pActual

)

{

HRESULT hr = CMemAllocator::SetProperties(pRequest, pActual);

if (FAILED(hr))

{

return hr;

}

ALLOCATOR_PROPERTIES *pRequired = &(m_pPin->m_allocprops);

if (pRequest->cbAlign != pRequired->cbAlign)

{

return VFW_E_BADALIGN;

}

if (pRequest->cbPrefix != pRequired->cbPrefix)

{

return E_FAIL;

}

if (pRequest->cbBuffer > pRequired->cbBuffer)

{

return E_FAIL;

}

if (pRequest->cBuffers > pRequired->cBuffers)

{

return E_FAIL;

}

*pActual = *pRequired;

m_lCount = pRequired->cBuffers;

m_lSize = pRequired->cbBuffer;

m_lAlignment = pRequired->cbAlign;

m_lPrefix = pRequired->cbPrefix;

return S_OK;

}

The Alloc method is called when the allocator object needs to allocate the media samples. For this allocator, the application provides the memory buffer, so the allocator object doesn’t need
to allocate any buffer memory. However, the allocator object does need to create the media sample objects. An IMediaSample object manages a pointer to a block of memory, but the block is allocated and released independently.

The Alloc method determines whether storage has already been allocated for the media samples; if the requirements haven’t changed since the previous call to Alloc, the method returns without
doing anything. Otherwise, the media samples are released and a new series of CMediaSample objects are created. Each CMediaSample holds a pointer that points to a section of the memory buffer.
The ReallyFree method has a deceptive name. Although this method is supposed to free the buffers allocated by the allocator object, in this case it won’t because those buffers haven’t
been allocated by this object. However, because Alloc did create m_lAllocated CMediaSample objects, the ReallyFree method must release those objects.

Finally the SetProperties method specifies the number of buffers to be allocated and the size of each buffer. This is a request, not a command, so SetProperties returns the actual number and size of buffers
allocated by the call, which might be at variance with the number requested. You should always check the returned value. The parameters are passed in an ALLOCATOR_PROPERTIES structure, which has the following definition:

点击(此处)折叠或打开

typedef struct _AllocatorProperties {

long cBuffers;

long cbBuffer;

long cbAlign;

long cbPrefix;

} ALLOCATOR_PROPERTIES;

The cBuffers field specifies the number of buffers to be created by the allocator, while cbBuffer specifies the size of each buffer in bytes. The cbAlign field specifies the byte alignment of each buffer, and cbPrefix allocates
a prefix of a specific number of bytes before each buffer, which is useful for buffer header information. Because no allocation is taking place inside this allocator, SetProperties passes the allocation request to its parent class, CMemAllocator, and then
performs a series of checks to ensure that the allocations have been performed successfully. Because the allocator properties are set by the application, the request can’t exceed these properties. If any of these checks fail, a failure is reported to the caller.
If all the checks passed successfully, SetProperties returns the actual allocation values to the caller.

Defining and Implementing the Input Pin Class
The DirectShow filter implementation provides an overridden implementation of the class CTransInPlaceInputPin, which implements all the methods required for handling input pin connections on CTransInPlaceFilter objects. Here’s
the definition of that class, CSampleGrabberInPin:

点击(此处)折叠或打开

class CSampleGrabberInPin : public CTransInPlaceInputPin

{

friend class CSampleGrabberAllocator;

friend class CSampleGrabber;

CSampleGrabberAllocator * m_pPrivateAllocator;

ALLOCATOR_PROPERTIES m_allocprops;

BYTE * m_pBuffer;

BOOL m_bMediaTypeChanged;

protected:

CSampleGrabber * SampleGrabber( ) { return (CSampleGrabber*) m_pFilter; }

HRESULT SetDeliveryBuffer( ALLOCATOR_PROPERTIES props, BYTE * m_pBuffer );

public:

CSampleGrabberInPin( CTransInPlaceFilter * pFilter, HRESULT * pHr )

: CTransInPlaceInputPin( TEXT("GrabberSampleInputPin\0"), pFilter,

pHr, L"Input\0" )

, m_pPrivateAllocator( NULL )

, m_pBuffer( NULL )

, m_bMediaTypeChanged( FALSE )

{

memset( &m_allocprops, 0, sizeof( m_allocprops ) );

}

~CSampleGrabberInPin( )

{

if( m_pPrivateAllocator ) delete m_pPrivateAllocator;

}

// override to provide major media type for fast connects

HRESULT GetMediaType( int iPosition, CMediaType *pMediaType );

// override this or GetMediaType is never called

STDMETHODIMP EnumMediaTypes( IEnumMediaTypes **ppEnum );

// override this to refuse any allocators besides

// the one the user wants, if this is set

STDMETHODIMP NotifyAllocator( IMemAllocator *pAllocator, BOOL bReadOnly );

// override this so we always return the special allocator,

// if necessary

STDMETHODIMP GetAllocator( IMemAllocator **ppAllocator );

HRESULT SetMediaType( const CMediaType *pmt );

// we override this to tell whoever's upstream of us what kind of

// properties we're going to demand to have

STDMETHODIMP GetAllocatorRequirements( ALLOCATOR_PROPERTIES *pProps );

};

Most of the variables and methods in CSampleGrabberInPin concern the allocation of buffers for stream data transfer with an upstream pin, but three methods, GetMediaType, EnumMediaTypes, and SetMediaType, are used during the
negotiation of the media type in the pin-to-pin connection process. The constructor,
CSampleGrabberInPin::CSampleGrabberInPin, calls its parent’s constructor, CTransInPlaceInputPin::CTransInPlaceInputPin, clears a few variables, and zeroes some memory, while the
destructor simply frees an associated CSampleGrabberAllocator object if one was created by the pin. Here are the method implementations for the rest of the class:

点击(此处)折叠或打开

HRESULT CSampleGrabberInPin::GetMediaType( int iPosition,

CMediaType * pMediaType )

{

CheckPointer(pMediaType,E_POINTER);

if (iPosition < 0) {

return E_INVALIDARG;

}

if (iPosition > 0) {

return VFW_S_NO_MORE_ITEMS;

}

*pMediaType = CMediaType( );

pMediaType->SetType( ((CSampleGrabber*)m_pFilter)->m_mtAccept.Type( ) );

return S_OK;

}

STDMETHODIMP CSampleGrabberInPin::EnumMediaTypes( IEnumMediaTypes **ppEnum )

{

CheckPointer(ppEnum,E_POINTER);

ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *));

// if the output pin isn't connected yet, offer the
possibly

// partially specified media type that has been set by the user

if( !((CSampleGrabber*)m_pTIPFilter)->OutputPin( )->IsConnected() )

{

This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
it. Thanks

// Create a new reference counted enumerator

*ppEnum = new CEnumMediaTypes( this, NULL );

return (*ppEnum) ? NOERROR : E_OUTOFMEMORY;

}

// if the output pin is connected,

// offer it's fully qualified media type

return ((CSampleGrabber*)m_pTIPFilter)->OutputPin( )->

GetConnected()->EnumMediaTypes( ppEnum );

}

STDMETHODIMP CSampleGrabberInPin::NotifyAllocator

( IMemAllocator *pAllocator, BOOL bReadOnly )

{

if( m_pPrivateAllocator )

{

if( pAllocator != m_pPrivateAllocator )

{

return E_FAIL;

}

else

{

// if the upstream guy wants to be read only and we
don't,

// then that's bad

// if the upstream guy doesn't request read only,

// but we do, that's okay

if( bReadOnly && !SampleGrabber( )->IsReadOnly( ) )

{

return E_FAIL;

}

}

}

This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
it. Thanks

return CTransInPlaceInputPin::NotifyAllocator( pAllocator, bReadOnly );

}

STDMETHODIMP CSampleGrabberInPin::GetAllocator( IMemAllocator **ppAllocator )

{

if( m_pPrivateAllocator )

{

CheckPointer(ppAllocator,E_POINTER);

*ppAllocator = m_pPrivateAllocator;

m_pPrivateAllocator->AddRef( );

return NOERROR;

}

else

{

return CTransInPlaceInputPin::GetAllocator( ppAllocator );

}

}

HRESULT CSampleGrabberInPin::GetAllocatorRequirements

( ALLOCATOR_PROPERTIES *pProps )

{

CheckPointer(pProps,E_POINTER);

if (m_pPrivateAllocator)

{

*pProps = m_allocprops;

return S_OK;

}

else

{

return CTransInPlaceInputPin::GetAllocatorRequirements(pProps);

}

}

HRESULT CSampleGrabberInPin::SetDeliveryBuffer( ALLOCATOR_PROPERTIES props,

BYTE * pBuffer )

{

// don't allow more than one buffer

if( props.cBuffers != 1 )

{

return E_INVALIDARG;

}

if( !pBuffer )

{

return E_POINTER;

}

m_allocprops = props;

m_pBuffer = pBuffer;

// If there is an existing allocator, make sure that
it is released

// to prevent a memory leak

if (m_pPrivateAllocator)

{

m_pPrivateAllocator->Release();

m_pPrivateAllocator = NULL;

}

HRESULT hr = S_OK;

m_pPrivateAllocator = new CSampleGrabberAllocator( this, &hr );

if( !m_pPrivateAllocator )

{

return E_OUTOFMEMORY;

}

m_pPrivateAllocator->AddRef( );

return hr;

}

HRESULT CSampleGrabberInPin::SetMediaType( const CMediaType *pmt )

{

m_bMediaTypeChanged = TRUE;

return CTransInPlaceInputPin::SetMediaType( pmt );

}

The GetMediaType method returns a pointer to a CMediaType object. We haven’t covered this class before. The CMediaType class is a wrapper for the AM_MEDIA_TYPE structure, which provides accessor methods (such as SetType, SetSubtype,
and GetSampleSize) for the fields in the AM_MEDIA_TYPE structure. In addition, the class overrides the assignment operator = and comparison operators != and ==, so when testing one instance of a CMediaType object with another, a test is made of each of the
fields within the AM_MEDIA_TYPE structure.

The EnumMediaTypes method returns an enumerated list within an IEnumMediaTypes object of all the media types that the input pin will propose for the connection. Although CBasePin (the ancestor class for all pin objects) implements
this functionality, CTransInPlaceInputPin (the ancestor class for this pin) overrides this behavior, so no types are returned when the input pin is unconnected. The Grabber Sample overrides this (yet again) so that media types are always returned, whether
the pin is connected or unconnected.

If the output pin on the filter has already been connected, it has already negotiated its media type with a downstream filter. In that situation, the media type of the output pin is returned by EnumMediaTypes as the type required
by the input pin. If the output pin isn’t connected, the method instantiates an object of class CEnumMediaTypes (IEnumMediaTypes is an interface to CEnumMediaTypes) and returns a pointer to it. Because the constructor is passed with a reference to the CSampleGrabberInPin
object, any media type established on the pin—perhaps by an earlier invocation of the SetMediaTypes method—will be returned in the enumeration.

The next few methods handle stream buffer allocation for the pin. The NotifyAllocator method determines whether the allocation is private—does it belong to the filter? If the application has allocated the memory for the pool
of samples, the Grabber Sample must insist on using that allocator. If so, it then determines whether the upstream filter’s output pin is requesting a read-only buffer. A read-only buffer is acceptable only if the Grabber Sample filter is not modifying the
data. (The m_bModifiesData flag is set in the constructor if the buffer will be changed during the callback function.) If that test passes successfully or if the buffer is not private, the method is handed off to the parent, CTransInPlaceInputPin::NotifyAllocator.
The GetAllocator method returns a pointer to the IMemAllocator object owned by the CSampleGrabberInPin object if the allocator is private; otherwise, it passes the request to the parent method, CTransInPlaceInputPin::GetAllocator. Much the same happens in
the GetAllocatorRequirements method. If the allocation is private, a pointer to m_allocprops is returned; otherwise, the request is passed to
CTransInPlaceInputPin::GetAllocatorRequirements.

Allocation for the pin is handled through the SetDeliveryBuffer method, which stores its passed parameters in the m_allocprops and m_pBuffer variables within the CSampleGrabberInPin object. The method then releases any private
allocators and instances a new private allocator, creating a new CSampleGrabberAllocator. In this case, the memory allocation is handled by the application, which passes a pointer to the buffer in pBuffer. Finally the media type for the input pin is set through
a call to SetMediaType, which simply sets the local Boolean variable m_bMediaTypeChanged to true and then calls the parent method CTransInPlaceInputPin::SetMediaType. The parent classes do all the media type checking, which eventually resolves into a call
to CGrabberSample::CheckInputType. The implementation of the input pin is now complete. Together with the allocator, we have everything in place to implement the filter class.

There’s just a little more definition work required to finish the entire specification of the filter. Here it is:

点击(此处)折叠或打开

const AMOVIESETUP_PIN psudSampleGrabberPins[] =

{ { L"Input" // strName

, FALSE // bRendered

, FALSE // bOutput

, FALSE // bZero

, FALSE // bMany

, &CLSID_NULL // clsConnectsToFilter

, L"" // strConnectsToPin

, 0 // nTypes

, NULL // lpTypes

}

, { L"Output" // strName

, FALSE // bRendered

, TRUE // bOutput

, FALSE // bZero

, FALSE // bMany

, &CLSID_NULL // clsConnectsToFilter

, L"" // strConnectsToPin

, 0 // nTypes

, NULL // lpTypes

}

};

const AMOVIESETUP_FILTER sudSampleGrabber =

{ &CLSID_GrabberSample // clsID

, L"Grabber Example Filter" // strName

, MERIT_DO_NOT_USE // dwMerit

, 2 // nPins

, psudSampleGrabberPins

}; // lpPin

// Needed for the CreateInstance mechanism

CFactoryTemplate g_Templates[]=

{

{ L"Grabber Example Filter"

, &CLSID_GrabberSample

, CSampleGrabber::CreateInstance

, NULL

, &sudSampleGrabber }

};

STDAPI DllRegisterServer()

{

return AMovieDllRegisterServer2(TRUE);

}

STDAPI DllUnregisterServer()

{

return AMovieDllRegisterServer2(FALSE);

}

//

// DllEntryPoint

//

extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);

BOOL APIENTRY DllMain(HANDLE hModule,

DWORD dwReason,

LPVOID lpReserved)

{

return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);

}

Finally, in a separate file, grabber.def, we define the
DLL entry points for our filter.

LIBRARY grabber

EXPORTS

DllMain PRIVATE

DllGetClassObject PRIVATE

DllCanUnloadNow PRIVATE

DllRegisterServer PRIVATE

DllUnregisterServer PRIVATE

These data structures—AMOVIESETUP_PIN and AMOVIESETUP_FILTER—are used to identify the specifics of the filter to DirectShow. For example, the strName field of AMOVIESETUP_FILTER is the filter name as it shows up in GraphEdit
or in any DirectShow enumeration of available filters. Changing that field will change the name as it appears in GraphEdit and across DirectShow.

Now, because we’re creating a system-wide object, we need to add some entry points common to Microsoft Windows dynamic-link libraries (DLLs).
Finally, in a separate file, grabber.def, we define the DLL entry points for our filter.
When the project is compiled, you’ll end up with a module named grabber.ax, which is a DLL object that must be registered with the system using the regsvr32.exe command. At the command line (and in the directory containing
grabber.ax), type regsvr32 grabber.ax .

If all goes smoothly, you’ll be informed that the DLL was successfully installed. The Grabber Sample filter is now available to all applications, including GraphEdit. Because you also have the class ID and interface ID for
the filter, you can begin to use it in all your DirectShow applications.

Summary
In this chapter, we’ve covered the Sample Grabber from both the application side—that is, the use of the Sample Grabber to provide access to the media stream in your own DirectShow applications—and the Sample Grabber architecture.
The design of the Sample Grabber is unusual, but it casts a lot of light on the internal design of DirectShow, the filter graph, and filters. With the Grabber Sample, you’ve begun to explore how application and filter come together in a synergistic way. Now
we’ll move on to a source filter and learn how media samples are created.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐