When I say semi-automatically built graphs, this can mean a few things. We
could have a filter graph that is automatically generated, but we insert or
change some filters. We could also create a source filter, add it to the graph
(connect to some transform filters if we need to), and let graphbuilder
automatically create the rest of the graph. Finally, we could add some specific
filters we want to use to the graph, and let graphbuilder render the entire
graph and it will try to use the filters we've created. I'll quickly go over
each one now.
Let's say we're still just playing a .WAV file. But this time, we want to run
it through a transform filter to apply an effect to it. We'll use the Gargle
filter since it's a sample provided with the DShow SDK. Note that sample
filters need to be built and registered before they can be used, so make sure
you build the Gargle sample and run 'regvsr32' on it before you try to create a
Gargle filter. Our filter graph will look like so:
[source filters]--[wave parser]--[gargle effect]--[sound renderer]
We let graphbuilder create the default graph (without gargle filter). Then we
enumerate filters and look for the renderer filter. We know that uncompressed
sound is passed into the sound renderer from the filter it's connected to, and
that the gargle effect input pin accepts uncompressed sound data also. So we
can just slip the gargle filter in ahead of the renderer. For transform
filters, this is a good way to insert them. You may want to insert an effect
into a graph for mp3's, or AIFF files, and you can be pretty confident that the
renderer's input is always going to be a format that the effect filter can take
as input.
We can find the render filter either by looking for it by name, or by checking
number of output pins. The only filter in a completed graph to have no output
pins should be the renderer. In order to make our code work with different
renderers (remember there are at least 2 different audio renderers that come
with DirectShow), we don't want to look for it by name. So we'll find it by
checking for output pins. Once we have found the renderer filter, we'll connect
the gargle filters pins.
Here's the code to do this:
IGraphBuilder* g_pGraphBuilder; // assume already created
void BuildGraph()
{
IEnumFilters* EnumFilters;
IBaseFilter* Renderer;
IBaseFilter* Gargle;
IEnumPins* EnumPins;
bool FoundRenderer = false;
IPin* InPin; // renderer input
IPin* OutPin; // decoder or other filter output;
IPin* GargleIn;
IPin* GargeOut;
ULONG fetched;
PIN_INFO pinfo;
int numoutputpins = 0;
g_pGraphBuilder->RenderFile(L"blah.wav"); // 'L' macro makes it WCHAR like we need it to be
g_pGraphBuilder->EnumFilters(&EnumFilters);
EnumFilters->Reset();
while (FoundRenderer == false)
{
EnumFilters->Next(1, &Renderer, &fetched); // get next filter
Renderer->EnumPins(&EnumPins);
EnumPins->Reset();
numoutputpins = 0;
while (EnumPins->Next(1, &InPin, &fetched) == S_OK)
{
InPin->QueryPinInfo(&pinfo);
pinfo.pFilter->Release();
InPin->Release();
if (pinfo.dir == PINDIR_OUTPUT)
{
numoutputpins++;
break; // we can jump out if we found an output pin
}
}
EnumPins->Release();
if (numoutputpins == 0)
FoundRenderer = true;
else
Renderer->Release();
}
EnumFilters->Release();
// Find renderer input
Filter->EnumPins(&EnumPins);
EnumPins->Reset();
EnumPins->Next(1, &InPin, &fetched); // first one is only one
EnumPins->Release();
// Find ouput pin on filter it is connected to
Pin->ConnectedTo(&OutPin);
// Disconnect the filters - note that we have to call Disconnect for both pins
g_pGraphBuilder->Disconnect(InPin);
g_pGraphBuilder->Disconnect(OutPin);
// Create Gargle filter - CLSID for gargle filter is defined in garguids.h in sample source
CoCreateInstance(CLSID_Gargle, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&Gargle);
g_pGraphBuilder->AddFilter(Gargle, NULL);
// get it's pins
Gargle->EnumPins(&EnumPins);
EnumPins->Reset();
EnumPins->Next(1, &GargleIn, &fetched);
GargleIn->QueryPinInfo(&pinfo);
pinfo.pFilter->Release();
if (pinfo.dir = PINDIR_OUTPUT)
{
GargleOut = GargleIn;
EnumPins->Next(1, &GargleIn, &fetched);
}
else
EnumPins->Next(1, &GargleOut, &fetched);
// now connect pins
g_pGraphBuilder->Connect(OutPin, GargleIn);
g_pGraphBuilder->Connect(GargleOut, InPin);
}
|
Well that was long and messy. At least we now have the graph the way we want
it, and it's independant of which decoder and renderer are being used. Notice
that in order to get the output pin of the filter that was previously connected
to the renderer, we just call the renderer input pin's IPin::ConnectedTo().
The second way we can create semi-automatically built graphs makes use of some
things we know about how graphbuilder creates graphs. We know that while
GraphBuilder is attempting to create a graph, it tries to connect the current
filter its working with, to any filter already in the graph before it starts
adding new filters to the graph. This means that we can create a Gargle filter,
add it to the graph, and call IGraphBuilder::RenderFile(), and graphbuilder will
automatically stick the Gargle filter in the first place it will connect.
Here's basically how we do this:
IGraphBuilder* g_pGraphBuilder; // already created
void Buildgraph()
{
IBaseFilter* Gargle;
CoCreateInstance(CLSID_Gargle, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&Gargle);
g_pGraphBuilder->AddFilter(Gargle, NULL);
g_pGraphBuilder->RenderFile(L"blah.wav");
}
|
That's it. You can do this same thing in GraphEdit to see that it works. Some
filters, for 1 reason or another, will not automatically be inserted. The best
way to ensure that a filter ends up where you want it is to add it to the graph
first, then enumerate it's pins and check if they are connected. If not, then
go through the method of insertimg it manually before the renderer as we did
earlier.
The last way to create a semi-automatically generated graph is to create the
first filter (or filters) and render an output pin. Let's say this time we want
to play a .WAV file from a website. Instead of the regular 'File Source
(Async)' filter, we need the 'File Source (URL)'. What we need to do is create
the source filter explicitly, then we can call IGraphBuilder::Render() on it's
output pin. We're not restricted to calling Render() on source filters. Any
time we have a partial graph (a source filter connected to any number of
transform filters), we can call Render() on the unconnected output pin of the
last filter in line, and graphbuilder will attempt to complete the graph for us.
Here's the way that would go:
// our own definition of 'File Source (URL)' based on CLSID we found in registry
#define INITGUID // have to define this so next line actually creates GUID structure
DEFINE_GUID(CLSID_FileSourceURL,
0xE436EBB6, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);
IGraphBuilder* g_pGraphBuilder; // already created
void BuildGraph()
{
IBaseFilter* Source;
IFileSourceFilter* FileSource;
IEnumPins* EnumPins;
IPin* Pin;
CoCreateInstance(CLSID_FileSourceURL, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&Source);
g_pGraphBuilder->AddFilter(Source, NULL);
Source->QueryInterface(IID_IFileSourceFilter, (void**)&Source);
FileSource->Load("http://www.asite.com/blah.wav"); // set file to play
FileSource->Release();
Source->EnumPins(&EnumPins);
EnumPins->Reset();
EnumPins->Next(1, &Pin, &fetched);
EnumPins->Release();
g_pGraphBuilder->Render(Pin); // graphbuilder completes graph
Pin->Release();
Source->Release();
}
|
|