Programming Microsoft DirectShow for Digital Video and Television

Overview

From desktop to television screen, deliver broadcast-ready digital video with DirectShow—the Microsoft DirectX streaming API integrated in the Windows operating system. Digital video expert and VRML creator Mark Pesce walks you through core DirectShow capabilities for capturing, editing, and rendering media—demonstrating basic to advanced techniques for producing studio-quality results. Whether you’re looking to add simple playback to an application or create your own movies and features, you’ll get the tools, ...

See more details below
Available through our Marketplace sellers.
Other sellers (Paperback)
  • All (9) from $67.65   
  • New (5) from $67.65   
  • Used (4) from $79.40   
Close
Sort by
Page 1 of 1
Showing All
Note: Marketplace items are not eligible for any BN.com coupons and promotions
$67.65
Seller since 2014

Feedback rating:

(2)

Condition:

New — never opened or used in original packaging.

Like New — packaging may have been opened. A "Like New" item is suitable to give as a gift.

Very Good — may have minor signs of wear on packaging but item works perfectly and has no damage.

Good — item is in good condition but packaging may have signs of shelf wear/aging or torn packaging. All specific defects should be noted in the Comments section associated with each item.

Acceptable — item is in working order but may show signs of wear such as scratches or torn packaging. All specific defects should be noted in the Comments section associated with each item.

Used — An item that has been opened and may show signs of wear. All specific defects should be noted in the Comments section associated with each item.

Refurbished — A used item that has been renewed or updated and verified to be in proper working condition. Not necessarily completed by the original manufacturer.

New
2003-04-23 Paperback New New, with CD ( sealed ), We ship one business day with tracking number. We do not ship to CANADA, GU, PR, Hawaii and Alaska.

Ships from: hayward, CA

Usually ships in 1-2 business days

  • Canadian
  • International
  • Standard, 48 States
  • Standard (AK, HI)
  • Express, 48 States
  • Express (AK, HI)
$97.20
Seller since 2008

Feedback rating:

(191)

Condition: New
0735618216 New. Looks like an interesting title!

Ships from: Naperville, IL

Usually ships in 1-2 business days

  • Standard, 48 States
  • Standard (AK, HI)
$170.26
Seller since 2014

Feedback rating:

(266)

Condition: New
Brand New Item.

Ships from: Chatham, NJ

Usually ships in 1-2 business days

  • Canadian
  • International
  • Standard, 48 States
  • Standard (AK, HI)
  • Express, 48 States
  • Express (AK, HI)
$175.00
Seller since 2014

Feedback rating:

(149)

Condition: New
Brand new.

Ships from: acton, MA

Usually ships in 1-2 business days

  • Standard, 48 States
  • Standard (AK, HI)
$175.00
Seller since 2014

Feedback rating:

(149)

Condition: New
Brand new.

Ships from: acton, MA

Usually ships in 1-2 business days

  • Standard, 48 States
  • Standard (AK, HI)
Page 1 of 1
Showing All
Close
Sort by
Sending request ...

Overview

From desktop to television screen, deliver broadcast-ready digital video with DirectShow—the Microsoft DirectX streaming API integrated in the Windows operating system. Digital video expert and VRML creator Mark Pesce walks you through core DirectShow capabilities for capturing, editing, and rendering media—demonstrating basic to advanced techniques for producing studio-quality results. Whether you’re looking to add simple playback to an application or create your own movies and features, you’ll get the tools, guidance, and ready-to-use media clips you need to get started now.

Learn how to:

  • Use the GraphEdit design tool to rapidly prototype applications
  • Write your own source, transform, and renderer filters
  • Capture audio and video from Webcams, digital video cameras, and TV tuners
  • Compress digital video streams onto disk
  • Place clips and tracks on a timeline with DirectShow Editing Services
  • Merge multiple video streams with the Video Mixing Renderer (VMR)
  • Synchronize audio and video
  • Create simple programs to play MP3, WAV, MIDI, AVI, and Microsoft Windows Media files
  • Use DirectX Media Objects (DMOs) for faster, lightweight development of effects, encoders, and decoders
  • Extend DirectShow with AVIs and Windows Media Format

CD inside Includes reusable media and code samples

CD features:

  • Microsoft DirectX 9.0 Software Development Kit (SDK) and documentation
  • Music, a movie short, and other video and audio samples
  • All the book’s programs and code
Read More Show Less

Product Details

  • ISBN-13: 9780735618213
  • Publisher: Microsoft Press
  • Publication date: 2/12/2002
  • Edition description: Book & CD-ROM
  • Pages: 448
  • Product dimensions: 7.38 (w) x 9.00 (h) x 1.39 (d)

Meet the Author

Mark Pesce has been an engineer for nearly a quarter of a century and is the coinventor of VRML and the author of numerous books, including VRML: Browsing and Building Cyberspace, and The Playful World: How Technology Is Transforming Our Imagination.

Read More Show Less

Table of Contents

Introduction
Pt. I The Basics
1 DirectShow Concepts 3
2 GraphEdit 13
3 Programming DirectShow Applications 29
Pt. II Capture and Editing
4 Capturing Audio with DirectShow 51
5 Capturing Audio and Video from a Webcam 71
6 Capture and Output Using DV Camcorders 81
7 Working with TV Tuners and Pausing Live Video Streams 117
8 Editing Media Using DirectShow Editing Services 145
9 Using the Video Mixing Renderer 167
Pt. III DirectShow Filters for Video Processing
10 Writing a DirectShow Transform Filter 191
11 Using the Sample Grabber Filter 225
12 Writing DirectShow Source Filters 265
13 Creating and Working with DirectX Media Objects 289
Pt. IV Advanced Topics
14 Understanding the AVI File Format 311
15 Windows Media Applications 327
A MPEG Format Support in DirectShow 373
B The DirectShow Transform Filter Wizard 377
C Optimizing File Input and Output in Digital Media Applications 381
Index 389
Read More Show Less

First Chapter

  • COM Basics
    • Naming and GUIDs
    • Initializing and Releasing COM
    • Creating an Instance of a COM Object
    • Querying Interfaces in COM Objects
    • Using COM Objects
  • Configuration of Visual Studio .NET for DirectShow Programming
    • DSRender: A DirectShow Media Player in C++
    • Examining main
    • Understanding DSRender Line by Line
    • Saving a Filter Graph to a .GRF File
  • DSBuild: Building a Filter Graph (Mostly) Manually
    • Examining main, Again
    • Locating Pins and GetPin
    • Summary

3 Programming DirectShow Applications

Although sample DirectShow filter graphs can be constructed and tested in GraphEdit, application programmers want to use standard programming languages—either C or C++—to construct DirectShow applications. Although Visual Basic is an easy-to-learn and fully functional programming environment, the Visual Basic support for DirectShow programming interfaces is minimal. If you’re a Visual Basic programmer, don’t despair: nearly everything that follows is useful information, even if it can’t be directly applied to your programming needs. For the purposes of this text, we’ll be using the Microsoft Visual C++ integrated development environment, which provides a robust platform for the design and testing of DirectShow applications.

The design of a DirectShow application is straightforward and generally has three logical parts: initialization, where the application environment is established, followed by the construction of the DirectShow filter graph; execution, when the filter graph enters the running state and processes a stream of data; and cleanup, when data structures are deallocated and system resources released. This isn’t significantly different from the model used by any other Windows application, and as a result, DirectShow applications can be combined with existing Windows applications very easily.

Before we can dive in and create a simple "media player" using DirectShow, a player that can be used to play any media types for which there are corresponding DirectShow filters (at the very least, these will include AVI, WAV, and Windows Media files), we need to cover some ground for programmers unfamiliar with the Microsoft Component Object Model (COM) programming interfaces. The DirectShow programming interfaces to filters and filter graphs present themselves as COM objects, so most DirectShow applications are COM-intensive. Although this might sound daunting, you don’t need to know very much about COM to write fully functional DirectShow programs.

COM Basics

During the 1990s, as operating systems and application programs grew progressively more complex, software architects struggled with the need to produce reusable code objects that could, through clearly defined interfaces, be reused throughout an operating system or application program. Such objects could simplify the design of programs dramatically. Rather than rely on native functionality, which could dramatically increase the complexity of a program, the programmer could call on these reusable code objects as needed.

For example, a word processor and an e-mail client both need access to a spelling checker—so why write two spelling checkers when a reusable code object could be invoked by both programs when needed? In the ideal case, nothing would need to be known about the reusable code object other than its name. Once the programmer included this ideal object, the program would be able to query it to determine its properties. Like a normal object, this object would have data and methods, both of which would be accessible (if public) to the invoking program. In short, the ideal reusable code object would act just like code written by the programmer.

By the mid-1990s, Microsoft had introduced COM, its version of the reusable code object. Although the first generations of COM were somewhat rough in their design, nearly a decade of refinement has produced a level of functionality that begins to approach the ideal of the reusable code object. A COM object, like a C++ object, has properties that can be inspected, methods to be invoked, and interfaces that illustrate characteristics inherited from base classes. The creator of a COM object can choose to hide or reveal any of these qualities, producing an object that is both easy to manage and easy to use.

Naming and GUIDs

As in the case of the ideal reusable code object, the only thing you need to know about a COM object is its name. However, this name isn’t a string of Roman characters; it’s a globally unique identifier (GUID), a string of hexadecimal numbers, in the format of 32 bits–16 bits–16 bits–16 bits–44 bits. The GUID is guaranteed to be unique, so each COM object has a unique name. (This is true only for COM objects you’ve created yourself if you follow Microsoft’s rules for the creation of new GUIDs. Those details are available through the developer support section of Microsoft’s Web site.) Fortunately, you don’t have to remember these meaningless strings of numbers; each COM object used by DirectShow has been given a C++ defined name, also known as a class ID. The class ID is English-readable and easy to understand. For example, the COM object that represents a filter graph has the class ID CLSID_FilterGraph, which represents the GUID e436ebb8-542f-11ce-9f53-0020af0ba770 (and thank goodness for that). The class ID provides the symbolic name that you’ll use to instantiate a COM object.

Initializing and Releasing COM

Before COM can be used within a DirectShow application, the COM facilities must be initialized. (If you have multiple execution threads in your application, each thread must be initialized separately.) To initialize COM, add the following line of source code to the initialization routine of the application:

CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) //Initializes COM

After COM has been initialized, calls to the COM libraries can be made in any desired fashion. Before the application terminates its execution, COM must be shut down again. (Failure to shut down COM could result in execution errors when another program attempts to use COM services .) To release COM services, add this line of code to the application’s cleanup code:

CoUninitialize();       // Releases COM

No calls to the COM services can be made after COM has been uninitialized.

Creating an Instance of a COM Object

Once COM services have been initialized in DirectShow, you will likely make a number of COM invocations to create instances of COM objects. These calls will create various objects needed by the application, such as filters. One of the first COM invocations in any DirectShow application will generally be a call to create the Filter Graph Manager, an object that handles the internal details of the filter graph. (The filter graph isn’t an object per se, but a logical construction consisting of several COM objects working closely together.)

The COM routine CoCreateInstance is used to create COM objects. In the case of the Filter Graph Manager, the code to create it might look like this:

IGraphBuilder *graphBuilder = NULL;    // Pointer to created object
HRESULT hr = CoCreateInstance(CLSID_FilterGraph,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,
(void **)&pGraphBuilder);

The CoCreateInstance call takes five arguments, beginning with a class ID—in this case CLSID_FilterGraph—which requests that a COM object representative of a filter graph (really, a Filter Graph Manager) be created. The NULL parameter indicates that this is not an aggregate object, which will be the case in any DirectShow application. The value CLSCTX_INPROC_SERVER indicates the that the COM object is being loaded from an in-process (local to your application) DLL. This value is always present in this parameter.

The next argument is an interface ID, which informs COM of the unique interface being requested by the caller. In this case, the value is IID_IGraphBuilder, which means that you will be retrieving the object’s IGraphBuilder interface, which has methods for building filter graphs. Later, you’ll need to use another interface on the same object, IMediaControl, which provides methods to start, stop, and pause the graph. The pointer address is returned in the last parameter. This pointer is cast as void**, a generic pointer to a pointer, because the function could return a pointer to any number of objects.

Nearly every COM invocation returns a status code of some sort or another; this code should always be examined for error values, using the macros SUCCEEDED and FAILED to test for success or failure. A COM call that generates an error indicates either a logical error in the program or some failure of the operating system to fulfill a request, perhaps because resources already in use or as yet uninitialized have been requested by the program.

When you’re through with a COM interface, you need to invoke its Release method so that the object will know how to delete itself at the appropriate time. For the preceding code fragment, this method might look like this:

pGraphBuilder->Release();  // Release the object
pGraphBuilder = NULL; // And set it to NULL

If you fail to release COM interfaces, objects will not get deleted and you’ll clutter up your memory, suffer a performance hit, and possibly confuse the operating system into thinking that resources are being used after you’ve finished with them. So make sure you clean up after your COM invocations.

Querying Interfaces in COM Objects

After an object has been instantiated through a COM call, a DirectShow application will often need access to additional interfaces on the object. For example, if an application programmer wants to have control over the execution of the filter graph, a pointer to the Filter Graph Manager’s IMediaControl interface will have to be acquired. It’s the same object being manipulated in either case, but each interface presents unique methods and properties suited for a particular task.

To acquire this interface, you need to send a query (request) to the object using any of its interfaces that you have already obtained. In this case, we already have its IGraphBuilder interface, so we’ll use that interface. If we assume that the code fragment in the previous section has already executed successfully, that call might look like this:

IMediaControl *pMediaControl = NULL;    // Store pointer to interface
hr = pGraphBuilder->QueryInterface(IID_MediaControl,
(void**)&pMediaControl);

The QueryInterface method takes two parameters. The first parameter is the interface ID (a GUID) for the requested interface. In this case, the interface ID references the IMediaControl interface. The second parameter is a pointer to a storage location for the returned interface. Once again, an error code will be returned if the query fails.

Using COM Objects

For the most part, objects instantiated through calls to CoCreateInstance behave just as a standard, well-designed C++ object would. Once it’s been instantiated, a COM object can be treated much like any other C++ object that has been created on the heap. It must be released when it’s no longer needed, and it provides a portable container for properties and methods that will work across any application in the operating system’s environment. Every interface on a COM object inherits from the IUnknown interface, which, in addition to QueryInterface, has two other methods that control the object’s lifetime. Each time a COM object returns any of its interfaces to a client (such as your application) through the initial call to CoCreateInstance or later calls to QueryInterface, it calls its own AddRef method to increment its reference count. When a client is finished with an interface, it must call Release, and the COM object decrements its reference count by one. When the count reaches zero, meaning there are no outstanding interface pointers, the object deletes itself. That is why failure to call Release after you finish with an interface results in memory leaks.

All DirectShow filters are COM objects—including those you create for yourself—so when we get into the subject of writing your own filters, we’ll cover the internal construction of COM objects in much greater detail.

Configuration of Visual Studio .NET for DirectShow Programming

The development environment used in this book for DirectShow applications is the Microsoft Visual Studio .NET integrated development environment. The Visual C++ and Visual Basic programming languages are included in Visual Studio .NET, and they provide the raw platform for the creation of Windows applications.

Beyond Visual Studio .NET, the DirectX 9.0 Software Development Kit (SDK) is an essential element in the creation of DirectShow applications. The DirectX 9.0 SDK contains all the source files, headers, and libraries (along with a lot of helpful documentation) that will need to be linked with your own source code to create a functional DirectShow application.

If you already have Visual Studio .NET installed on the computer you’ll be using for DirectShow application development, you might need to check whether the correct DirectX 9.0 SDK directories are in the include paths for the Visual C++ compiler and linker. (You’ll know pretty quickly if your environment hasn’t been set up correctly because your applications will generate errors during the compile or linking phases of program generation.)

To inspect the settings for your projects, open the Property Pages dialog box for your Visual C++ project. In the C/C++ folder, examine the value of the field labeled Additional Include Directories. The file path for the DirectX 9.0 SDK include files should be the first value in that field.

After you’ve ensured that the DirectX 9.0 SDK include files are available to the compiler, click on the folder labeled Linker and examine the value of the field Additional Dependencies. Here you should find a file path that points to the DirectX 9.0 SDK object libraries. If you don’t, add the file path to the list of other file paths (if any) in the field.

At this point, Visual Studio .NET is ready for your programming projects. To test it, open the project DSRender (on the CD-ROM) and try to build it. If it compiles and executes without errors, everything has been set up correctly. If you have problems, ensure that the DirectX 9.0 SDK has been installed correctly.

Now, with all these important essentials out of the way, let’s take a look at DSRender, our first peek at a DirectShow application program. Like many of the other projects presented in this book, it’s designed for console-mode operation. This means that many of the Windows API calls that deal with the particulars of the graphical user interface—windows, menus, dialog boxes, and the like—have been left out of the project, leaving only the meat of the DirectShow application. The code samples provided with this book are designed to become the kernels of your own DirectShow applications, so the code is "clean" and uncluttered by the requirements of a fully loaded Windows application.

DSRender: A DirectShow Media Player in C++

DSRender, one of the simplest DirectShow applications to write, would be among the most difficult to engineer without DirectShow. The application’s action is straightforward: it displays an Open File dialog box, allows the user to select a file, and then attempts to render that file on the user’s computer. The media type doesn’t matter—it could be an AVI movie, a WAV sound, Windows Media, an MP3 song, or an MPEG movie. As long as there are DirectShow filters to handle the specifics of the file format, the file will be rendered.

Examining main

The source code file DSRender.cpp has only three functions, including the standard C/C++ function main, the entry point for the program. It’s the only function of interest to us, so here it is in all its (brief) glory:

// DSRender.cpp
// A very simple program to render media files using DirectShow
//
int main(int argc, char* argv[])
{
IGraphBuilder *pGraph = NULL; // Graph builder interface
IMediaControl *pControl = NULL; // Media control interface
IMediaEvent *pEvent = NULL; // Media event interface

if (!GetMediaFileName()) { // Local function to get a file name
return(0); // If we didn’t get it, exit
}

// Initialize the COM library.
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{
// We’ll send our error messages to the console.
printf("ERROR - Could not initialize COM library");
return hr;
}

// Create the Filter Graph Manager and query for interfaces.
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr)) // FAILED is a macro that tests the return value
{
printf("ERROR - Could not create the Filter Graph Manager.");
return hr;
}

// Use IGraphBuilder::QueryInterface (inherited from IUnknown)
// to get the IMediaControl interface.
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
if (FAILED(hr))
{
printf("ERROR - Could not obtain the Media Control interface.");
pGraph->Release(); // Clean up after ourselves
pGraph = NULL;
CoUninitialize(); // And uninitialize COM
return hr;
}

// And get the Media Event interface, too.
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
if (FAILED(hr))
{
printf("ERROR - Could not obtain the Media Event interface.");
pGraph->Release(); // Clean up after ourselves
pControl->Release();
CoUninitialize(); // And uninitialize COM
return hr;
}

// To build the filter graph, only one call is required.
// We make the RenderFile call to the Filter Graph Manager
// to which we pass the name of the media file.
#ifndef UNICODE
WCHAR wFileName[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, g_PathFileName, -1, wFileName,
MAX_PATH);
// This is all that’s required to create a filter graph
// that will render a media file!
hr = pGraph->RenderFile((LPCWSTR)wFileName, NULL);
#else
hr = pGraph->RenderFile((LPCWSTR)g_PathFileName, NULL);
#endif

if (SUCCEEDED(hr))
{
// Run the graph.
hr = pControl->Run();
if (SUCCEEDED(hr))
{
// Wait for completion.
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);

// Note: Do not use INFINITE in a real application
// because it can block indefinitely.
}

// And stop the filter graph.
hr = pControl->Stop();

// Before we finish, save the filter graph to a file.
SaveGraphFile(pGraph, L"C:\\MyGraph.GRF");
}

// Now release everything and clean up.
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();

return 0;
}

Understanding DSRender Line by Line

We’ve walked through much of the code in the section "COM Basics" earlier in this chapter. The application enters at main, sets up storage for a few variables (pointers to COM objects), and gets a file name with a call to the local function GetMediaFileName (peek at the source code if you need to see the details of that basic Windows function), and then initializes COM with a call to CoInitialize.

If all of this has proceeded successfully (and it should), the application next instantiates a Filter Graph Manager object with a call to CoCreateInstance, and obtains in that same call the IGraphBuilder interface on that object, which provides methods that allow you to build a filter graph. Once the IGraphBuilder interface has been obtained (if this fails, this might indicate problems with DirectX or the operating system), two QueryInterface method calls are made to retrieve additional interfaces that are exposed by the Filter Graph Manager. The first of these calls returns an IMediaControl interface, which has methods for changing the execution state of the filter graph, as explained previously. The second of these calls requests an IMediaEvent object. The IMediaEvent interface provides a way for the filter graph to signal its own state changes to the DirectShow application. In this case, IMediaEvent will be used to track the progress of media playback, and it will pause execution of the application until playback is done. (For operating system geeks: this is possible because the DirectShow filters execute in a different thread from the DirectShow application.)

Now some magic happens. With just a single line of code, the entire filter graph is built. When the IGraphBuilder method RenderFile is invoked (with the name of the media file), the Filter Graph Manager object examines the media file’s type and determines the appropriate set of filters—source, transform, and renderer—that need to be added to the filter graph. These filters are added to the filter graph and then connected together. If RenderFile returns without errors, DirectShow found a path from source to renderer. If the call to RenderFile fails, DirectShow lacked the filters to play the media file—or perhaps the file was corrupted.

With the filter graph built, a one-line call to the IMediaControl interface invoking its Run method begins execution of the filter graph. Although the filter graph begins executing, the Run method returns immediately because the data streaming code is running in a separate thread that has been started by the source filter. Media file playback commences. If the media file is a movie, a playback window will open on the display; if it’s a sound file, there won’t be any visible sign of playback, but sounds should start coming from the computer’s speakers. Figure 3-1 shows an AVI file being played.

Figure 3-1 DSRender playing the AVI file Sunset.avi (Image unavailable)

This application, as written, needs to pause during playback of the media file. If it didn’t, the application would terminate just after the filter graph had started playback, and that wouldn’t be very useful. This is where the IMediaEvent interface comes into play. Invoking its WaitForCompletion method with a value of INFINITE causes the application to wait until the Filter Graph Manager learns that the media file has completed its playback. In a real-world application, you wouldn’t use a value of INFINITE in the call to WaitForCompletion; if something happened to stall or halt the playback of the media file, the application would wait—forever. This is fine for a first DirectShow example, but other programming examples in this book will show you how to exploit the IMediaEvent interface more effectively.

After playback is complete, a call to the Stop method of the IMediaControl object halts the execution of the filter graph. This stop call is necessary because a filter graph doesn’t stop by itself when a media file has been fully rendered.

Saving a Filter Graph to a .GRF File

Immediately after the filter graph is stopped, you’ll see a call to a local function, SaveFilterGraph, which takes two arguments: a pointer to the IGraphBuilder interface and a file name. SaveFilterGraph saves a GraphEdit-viewable copy of the filter graph, so you can see the filter graph that’s been built by DSRender. Here’s the code for SaveFilterGraph:

// Pass it a file name in wszPath, and it will save the filter graph
// to that file.
HRESULT SaveGraphFile(IGraphBuilder *pGraph, WCHAR *wszPath)
{
const WCHAR wszStreamName[] = L"ActiveMovieGraph";
HRESULT hr;
IStorage *pStorage = NULL;

// First, create a document file that will hold the GRF file
hr = StgCreateDocfile(
wszPath,
STGM_CREATE | STGM_TRANSACTED | STGM_READWRITE |
STGM_SHARE_EXCLUSIVE,
0, &pStorage);
if(FAILED(hr))
{
return hr;
}

// Next, create a stream to store.
IStream *pStream;
hr = pStorage->CreateStream(
wszStreamName,
STGM_WRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
0, 0, &pStream);
if (FAILED(hr))
{
pStorage->Release();
return hr;
}

// The IpersistStream::Save method converts a stream
// into a persistent object.
IPersistStream *pPersist = NULL;
pGraph->QueryInterface(IID_IPersistStream,
reinterpret_cast<void**>(&pPersist));
hr = pPersist->Save(pStream, TRUE);
pStream->Release();
pPersist->Release();
if (SUCCEEDED(hr))
{
hr = pStorage->Commit(STGC_DEFAULT);
}
pStorage->Release();
return hr;
}

This function is straightforward, although it uses a few components we haven’t yet encountered. Beginning with a call to the Windows function StgCreateDocfile, an output file is opened, creating an IStorage object (in other words, an object that exposes the IStorage interface) that represents the file. (Note that this is an example of a COM object that is not created directly through CoCreateInstance but rather through a helper function.) Next an IStream stream object is created; this stream is used to provide a data path to the output file. The magic in this function happens when the Filter Graph Manager’s IPersistStream interface is obtained by a call to the QueryInterface method of IGraphBuilder. The IPersistStream interface contains methods that create persistent stream objects, which can be written to a storage medium such as a file and retrieved later. When the Save method of IPersistStream is invoked—with a parameter that points to the IStream object—the filter graph data structure is written to the stream.

If all of this goes as planned, a call to the Commit method of the IStorage interface writes the data to disk. At this point, a "snapshot" of the filter graph has been written out. This program uses the hard-coded string C:\MyGraph.GRF as the file name, but this name can be modified by you to any system-legal file path and name. After you run DSRender you’ll find the file MyGraph.GRF on your hard disk. Double-click it and GraphEdit will launch; you’ll see the filter graph created by DSRender. This filter graph will vary, depending on the media type of the file being rendered. Figure 3-2 shows the MyGraph.GRF filter graph.

Figure 3-2 GraphEdit showing the filter graph MyGraph.GRF created by DSRender (Image unavailable)

DSRender is a very slapdash example of a DirectShow application—no frills, no extra UI details, just media playback. Yet a very broad set of media can be played with this simple application because the DirectShow IGraphBuilder object handles the hard work of selecting and connecting the appropriate filters together to create a functional filter graph. Now we need to move on and learn how to do the heavy lifting for ourselves, building a filter graph in C++ code line by line. Well, mostly….

DSBuild: Building a Filter Graph (Mostly) Manually

The DSBuild application does most of the heavy lifting involved in creating an audio player for a wide variety of audio formats—essentially any audio format supported by DirectShow, including the audio tracks of AVI and Windows Media movies. The application code creates a Filter Graph Manager object and then creates two filters: a source filter (which points to a disk file) and an audio renderer filter. Then, using the Intelligent Connect capability of the Filter Graph Manager, the application connects the output pin of the source filter to the input pin of the audio renderer, adding the necessary intermediate transform filters to provide a path between source and renderer. Once that path has been created, the filter graph begins execution, plays the media file until completion, and then stops.

Examining main, Again

The source code in file DSBuild.cpp has four functions, and three of these are nearly identical to their counterparts in DSRender.cpp. The extra function, GetPin, will be examined in detail in the section "Locating Pins and GetPin" a bit further along. We need to begin with a detailed examination of main, which initially looks a lot like the version in DSRender.cpp.

// DSBuild implements a very simple program to render audio files
// or the audio portion of movies.
//
int main(int argc, char* argv[])
{
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent *pEvent = NULL;
IBaseFilter *pInputFileFilter = NULL;
IBaseFilter *pDSoundRenderer = NULL;
IPin *pFileOut = NULL, *pWAVIn = NULL;

// Get the name of an audio or movie file to play.
if (!GetMediaFileName()) {
return(0);
}

// Initialize the COM library.
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{
printf("ERROR - Could not initialize COM library");
return hr;
}

// Create the Filter Graph Manager object and retrieve its
// IGraphBuilder interface.
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr))
{
printf("ERROR - Could not create the Filter Graph Manager.");
CoUninitialize();
return hr;
}

// Now get the media control interface...
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
if (FAILED(hr)) {
pGraph->Release();
CoUninitialize();
return hr;
}

// And the media event interface.
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
if (FAILED(hr)) {
pControl->Release();
pGraph->Release();
CoUninitialize();
return hr;
}

// Build the graph.
// Step one is to invoke AddSourceFilter
// with the file name we picked out earlier.
// Should be an audio file (or a movie file with an audio track).
// AddSourceFilter instantiates the source filter,
// adds it to the graph, and returns a pointer to the filter’s
// IBaseFilter interface.
#ifndef UNICODE
WCHAR wFileName[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, g_PathFileName, -1, wFileName, MAX_PATH);
hr = pGraph->AddSourceFilter(wFileName, wFileName, &pInputFileFilter);
#else
hr = pGraph->AddSourceFilter(wFileName, wFileName, &pInputFileFilter);
#endif

if (SUCCEEDED(hr)) {

// Now create an instance of the audio renderer
// and obtain a pointer to its IBaseFilter interface.
hr = CoCreateInstance(CLSID_DSoundRender, NULL,
CLSCTX_INPROC_SERVER, IID_IBaseFilter,
(void **)&pDSoundRenderer);

if (SUCCEEDED(hr)) {

// And add the filter to the filter graph
// using the member function AddFilter.
hr = pGraph->AddFilter(pDSoundRenderer, L"Audio Renderer");

if (SUCCEEDED(hr)) {

// Now we need to connect the output pin of the source
// to the input pin of the renderer.
// Obtain the output pin of the source filter.
// The local function GetPin does this.
pFileOut = GetPin(pInputFileFilter, PINDIR_OUTPUT);

if (pFileOut != NULL) { // Is the pin good?

// Obtain the input pin of the WAV renderer.
pWAVIn = GetPin(pDSoundRenderer, PINDIR_INPUT);

if (pWAVIn != NULL) { // Is the pin good?

// Connect the pins together:
// We use the Filter Graph Manager’s
// member function Connect,
// which uses Intelligent Connect.
// If this fails, DirectShow couldn’t
// render the media file.
hr = pGraph->Connect(pFileOut, pWAVIn);
}
}
}
}
}

if (SUCCEEDED(hr))
{
// Run the graph.
hr = pControl->Run();
if (SUCCEEDED(hr))
{
// Wait for completion.
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);

// Note: Do not use INFINITE in a real application
// because it can block indefinitely.
}
hr = pControl->Stop();
}

// Before we finish, save the filter graph to a file.
SaveGraphFile(pGraph, L"C:\\MyGraph.GRF");

// Now release everything we instantiated--
// that is, if it got instantiated.
if(pFileOut) { // If it exists, non-NULL
pFileOut->Release(); // Then release it
}
if (pWAVIn) {
pWAVIn->Release();
}
if (pInputFileFilter) {
pInputFileFilter->Release();
}
if (pDSoundRenderer) {
pDSoundRenderer->Release();
}
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();

return 0;
}

The opening lines of the function are essentially the same as those from DSRender. A Filter Graph Manager object is instantiated through a COM call, and subsequent QueryInterface calls return pointers to its IMediaControl and IMediaEvent interfaces. That’s everything needed to begin building the filter graph. At this point, we use a new method of IGraphBuilder, AddSourceFilter, which takes a file name as a parameter and returns a pointer to an IBaseFilter interface on the filter that was chosen and instantiated. The IBaseFilter interface is exposed by all DirectShow filters.

Next the audio renderer filter is created using CoCreateInstance, with a class ID value of CLSID_DSoundRender, which returns the IBaseFilter interface for that object. Once that filter has been created successfully, it is added to the filter graph with the ingeniously named IGraphBuilder method AddFilter. The AddFilter method takes two parameters. The first parameter is a pointer to the IBaseFilter interface on the filter to be added, while the second parameter is an application-defined string used to identify the filter. (You can use this string to name the filter whatever you like. This feature is particularly worthwhile when examining a filter graph in GraphEdit.)

Now we have two filters in the filter graph: a source filter pointing to the file and an audio output filter. They need to be connected together, probably through a path of transform filters. The transform filters required to connect source to renderer will vary by media type of the source file. Rather than examining the source file ourselves to determine what intermediate filters are needed (which would be a long and involved process), we’ll use the DirectShow Intelligent Connect feature to do the work for us.

To begin, we’ll need to obtain IPin interfaces—which, as the name suggests, are exposed by the pins on a filter—for both the output of the source filter and the input of the renderer. We use the local function GetPin (explained in detail in the next section) to obtain these interfaces on the pins we want to connect. Once we have both of these, we can invoke the IGraphBuilder method Connect. (Connect takes as parameters two pins; if successful, the method connects the two pins through some set of intermediate filters.) If the call to Connect fails, DirectShow wasn’t able to build a path between source and renderer, possibly because the media type of the source file isn’t supported by DirectShow or because the file didn’t contain any audio.

As in DSRender, the application uses the IMediaControl interface’s Run method to begin execution of the filter graph, and the IMediaEvent method WaitForCompletion pauses execution of the application until the media file has been completely rendered. At this point, the Stop method is called and the filter graph halts its execution. The filter graph is written to a file with a call to SaveGraphFile, the allocated interfaces are released, and the application terminates.

Even when created by hand, a filter graph isn’t a difficult object to build or maintain. However, this application would have been significantly more difficult to write without Intelligent Connect, which allowed us to ignore the specifics of the media in the source file.

Locating Pins and GetPin

The local function GetPin allows us to locate input and output pins on a filter and retrieve the IPin interface that allows us to control the pins. The code for the function is concise, as shown here:

// This code allows us to find a pin (input or output) on a filter.
IPin *GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir)
{
BOOL bFound = FALSE;
IEnumPins *pEnum;
IPin *pPin;

// Begin by enumerating all the pins on a filter
HRESULT hr = pFilter->EnumPins(&pEnum);
if (FAILED(hr))
{
return NULL;
}

// Now look for a pin that matches the direction characteristic.
// When we’ve found it, we’ll return with it.
while(pEnum->Next(1, &pPin, 0) == S_OK)
{
PIN_DIRECTION PinDirThis;
pPin->QueryDirection(&PinDirThis);
if (bFound = (PinDir == PinDirThis))
break;
pPin->Release();
}
pEnum->Release();
return (bFound ? pPin : NULL);
}

The IBaseFilter interface has a member function, EnumPins, which returns an IEnumPins interface. This interface enables you to iterate through a list of all the pins on a filter. Each element in the IEnumPins list contains an IPin object. As the code walks through this list of pins, each pin is queried through an invocation of its IPin::QueryDirection method. If the direction matches the requirements, that IPin interface pointer becomes the function’s return value—with one caveat: some filters have multiple input and output pins, and these pins can have different media types, so you can’t know that a returned IPin will be useful in every situation. You could call GetPin on a digital video filter, expecting to get an output pin for digital video, only to find that it won’t connect to a video renderer because the output pin is for the audio track that accompanies the video. This function doesn’t discriminate.

Summary

As you can see from DSRender and DSBuild, it’s not difficult to construct DirectShow applications with lots of functionality. The definitions and interfaces for the DirectShow objects are straightforward and easy to use. It’s this ease of use that makes DirectShow so powerful. In just a little more than 20 lines of code, you can build a fully functioning media player. All you need after that is user interface. That said, application programming for graphical user interfaces is time-consuming and generally takes more effort than the core of the application itself. Either of these examples can be dropped right into an existing Windows application pretty much as they are to provide broad media capabilities, although you would probably want to change the function names. Now, with the programming basics out of the way, we can move on to specific topics of interest, starting with the foundations of audio recording and playback.

Read More Show Less

Customer Reviews

Be the first to write a review
( 0 )
Rating Distribution

5 Star

(0)

4 Star

(0)

3 Star

(0)

2 Star

(0)

1 Star

(0)

Your Rating:

Your Name: Create a Pen Name or

Barnes & Noble.com Review Rules

Our reader reviews allow you to share your comments on titles you liked, or didn't, with others. By submitting an online review, you are representing to Barnes & Noble.com that all information contained in your review is original and accurate in all respects, and that the submission of such content by you and the posting of such content by Barnes & Noble.com does not and will not violate the rights of any third party. Please follow the rules below to help ensure that your review can be posted.

Reviews by Our Customers Under the Age of 13

We highly value and respect everyone's opinion concerning the titles we offer. However, we cannot allow persons under the age of 13 to have accounts at BN.com or to post customer reviews. Please see our Terms of Use for more details.

What to exclude from your review:

Please do not write about reviews, commentary, or information posted on the product page. If you see any errors in the information on the product page, please send us an email.

Reviews should not contain any of the following:

  • - HTML tags, profanity, obscenities, vulgarities, or comments that defame anyone
  • - Time-sensitive information such as tour dates, signings, lectures, etc.
  • - Single-word reviews. Other people will read your review to discover why you liked or didn't like the title. Be descriptive.
  • - Comments focusing on the author or that may ruin the ending for others
  • - Phone numbers, addresses, URLs
  • - Pricing and availability information or alternative ordering information
  • - Advertisements or commercial solicitation

Reminder:

  • - By submitting a review, you grant to Barnes & Noble.com and its sublicensees the royalty-free, perpetual, irrevocable right and license to use the review in accordance with the Barnes & Noble.com Terms of Use.
  • - Barnes & Noble.com reserves the right not to post any review -- particularly those that do not follow the terms and conditions of these Rules. Barnes & Noble.com also reserves the right to remove any review at any time without notice.
  • - See Terms of Use for other conditions and disclaimers.
Search for Products You'd Like to Recommend

Recommend other products that relate to your review. Just search for them below and share!

Create a Pen Name

Your Pen Name is your unique identity on BN.com. It will appear on the reviews you write and other website activities. Your Pen Name cannot be edited, changed or deleted once submitted.

 
Your Pen Name can be any combination of alphanumeric characters (plus - and _), and must be at least two characters long.

Continue Anonymously

    If you find inappropriate content, please report it to Barnes & Noble
    Why is this product inappropriate?
    Comments (optional)