天行健,君子以自强不息!
分类: WINDOWS
2012-07-29 09:50:13
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.
尽管DS filter graph的示例可以在GraphEdit中构建与测试,但是程序员还是要使用标准的编程语言—C或C++—来构建DS应用程序。尽管VB很容易上手而且也是完全函数式的编程环境,但是它只能支持DS程序接口中的很小的一部分。如果你真的是一个VB程序员,也不用绝望:下面要要讲的内容即使不能直接满足程序的要求,但几乎每项都是有用的信息。为了本书的内容实现,将使用VC++ IDE,它提供了一个强大的开发和测试DS应用程序的平台。
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.
DS应用程序的设计通常可以分成三个逻辑部分:初始化,用以建立应用程序环境;紧接着的是filter graph的构建;执行,filter graph进入运行状态并处理数据流;以及清除,销毁分配了的数据结构并释放系统资源。这些逻辑过程和别的Windows应用程序并没有很大的不同,因此,DS的应用程序能很容易地和已有的Windows应用程序结合起来。
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.
在潜心使用DS创建一个简单的媒体播放器—它能使用DS相应的filter来播放任何类型的媒体文件—之前,我们需要先熟悉一下微软的COM编程接口。DS的filter和filter graph的编程接口都是用COM对象表示的,因此大多数的DS应用程序都是COM相关实现。尽管听起来很吓人,但是DS编程不需要太多的COM知识。
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.
在上世纪90年代,随着操作系统和应用程序变得越来越复杂,软件架构就提出了通过明确定义接口的方式来重用代码对象的方案。这些对象大大地简化了程序设计。相对于原始的函数方式,这种重用代码对象的方式可以极大地增加程序的复杂性。
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.
到了90年代中期,微软引入了COM,一个可重用代码对象的实现。尽管第一代的COM在设计看起来有些简陋,但是随着后来近十多年的不断的功能提升,直到现在开始达到了可重用代码对象的目标。和C++对象一样,COM对象也有可以注入的属性,可以激活的方法,和可以继承自基类的描述属性的接口。COM对象在创建时可以选择隐藏或暴露这些特性,以利于对象的管理和使用。
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.
如前所述的理想化的可重用代码对象一样,COM对象只需要知道它的名字即可。但是,它的名字并不是罗马字符串;它是名字是一个全球唯一标识,GUID,一个十六进制的字符串,格式为 32 bits–16 bits–16 bits–16 bits–44 bits。GUID保证了它的唯一性,因此每个COM对象也就有了一个唯一名字。幸运的是,并不需要记住这些没有意义的数字串。每个DS使用的COM对象都会要赋值一个C++定义名,被称为类ID。这个类ID是英文可读的并且也容易理解。例如,表示filter graph的COM对象的类ID为CLSID_FilterGraph, 它所表示GUID是e436ebb8-542f-11ce-9f53-0020af0ba770, 这个类ID提供了可实例化的COM对象的符号化名字。
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:
在DS应用程序使用COM之前,需要先初始化(如果应用程序中有多个执行线程,那么每个线程都必须独立进行这个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:
初始化COM之后,就可以用想要的方式调用COM库。在结束应用程序的执行前,必须先关闭COM(如果COM关闭失败将会导致其它程序尝试使用COM服务执行出错)。释放COM服务,可以使用如下的代码:
No calls to the COM services can be made after COM has been uninitialized.
COM释放后,所有的COM服务都将不能调用。
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.)
一旦初始化了DS中的COM服务,就可以通过COM调用来创建COM对象的实例。这些调用可以为应用程序创建各种对象,如filter什么的。任何DS应用程序的第一个COM调用通常是创建Filter graph Manager(是无论单个功能的filter ,还是完整的DS应用程序吗?),一个用于处理filter graph的内部细节的对象(filter graph并不是对象本身,而是由多个紧密合作的COM对象的逻辑构成)。
The COM routine is used to create COM objects. In the case of the Filter Graph Manager, the code to create it might look like this:
COM例程CoCreateInstance可以用来创建COM对象。创建Filter Graph Manager的代码如下:
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 . This value is always present in this parameter.
CoCreateInstance有五个输入参数:
类ID: CLSID_FilterGraph , 表示要创建Filter Graph Manager的COM对象。
NULL: 表示这个COM对象不是一个聚合对象,它是DS应用程序的指定设置;
CLSCTX_INPROC_SERVER: 表示这个COM对象被进程内(即应用程序的)DLL加载,也是固定设置。
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.
接口ID: IID_IGraphBuilder, 表示将返回对象的IGraphBuilder接口,它提供了创建filter graph的方法。
同时还需要这个对象的另一方法,IMediaControl,用来启动,停止,和暂停graph。
Void**: 这个函数用来返回一个指向对象的指针。
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.
几乎每个COM调用都会有返回状态码,可以用SUCCEEDED和FAILED这两个宏来测试。COM调用的错误类型通常有程序的逻辑错误,对OS的请求失败,资源已被占用,或是请求未初始化的资源。
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:
当用完了一个COM接口,需要在适当的时候调用上述两个方法来释放。
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.
如果COM接口的释放失败,那么这个对象也不会被删除,这会导致内存失序并影响OS性能,还有可能导致即使程序结束了,OS还认为资源被使用。因此COM调用之后一定要进行释放。
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.
当一个对象通过COM调用实例化后,DS应用程序通常还需要访问这个对象的其它接口。例如,如果应用程序需要完全控制filter graph的执行,就需要指向Filter Graph Manager的IMediaControl接口的指针。这是同一个对象在不同情况下的控制,但是每个接口都有唯一的方法来用于不同的任务。
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:
为了获得对象的某个接口,需要给这个已创建的对象发送询问请求。
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.
QureyInterface方法接收两个参数:
接口ID: IMediaControl, 要询问接口的GUID;
返回接口的存储位置的指针。
和返回的状态码。
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.
对于绝大多数情况来说,通过调用CoCreateInstance来进行对象实例化是实现良好设计的C++对象的一种标准化的方式。当一个COM对象被实例化后,那么这个对象和其它的C++对象一样,在堆上进行了分配,当不再需要时就需要释放,并且它提供了可给OS中所有应用程序可用的属性和方法的可移植容器。每个COM对象的接口都是继承自IUnknown接口,除了QueryInterface外,它还提供了另外两个控制对象生命周期的方法:
AddRef方法:每当这个COM对象无论是通过CoCreateInstance的初始化调用还是通过QueryInterface的调用返
回任何它的接口给客户端(如调用它的应用程序)时,它都会调用这个方法来增加它的引用计数。
当客户端使用完了这个接口时,必须调用Release来释放,而COM对象的引用计数也会减一,当引用计数到零时,竟未着没有接口被引用了,这时对象才释放自身。这也是为什么在程序结束时未调用Release进行释放而导致内存泄露的原因。
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.
所有的DS filter无一例外都是COM对象—包括自己设计实现的filter—当进入到设计实现自己的filter这个主题时,再对COM对象的内部细节进行展开说明。
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.
本书的DS应用程序的开发环境为Visual C++。
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.
还需要DirectX 9.0 SDK。它包含有源文件,头文件,库,以及一些有用的示例和帮助文档。
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.)
如果安装好了Visual Studio .NET,那么再检查下DirectX 9.0 SDK的路径是否配置了。
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.
主要是检查Additional Include路径。
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 . 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.
再检查Additional Dependencies路径。
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.
如果上面的配置都整好了,就打开DSRender项目测试下。
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.
当上面的都搞好后,现在来正式看DSReader这个项目。如同本书中的其它项目一样,这个项目也是控制台操作。这意味着很多用于图形用户接口的Windows API调用—窗口,菜单,对话框等—都从这个项目中剔除了,只留下真正的DS应用程序。本书的代码示例都是DS应用程序的核心,因此这些代码是干净而整洁,可完全用于Windows应用程序。
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.
DSReader是一个很简单的DS应用程序。它的应用场景很简单:它将会开启一个用于打开文件的对话框,允许用户选择一个文件,然后尝试将这个文件渲染在用户的计算机上。也不用考虑媒体类型—它可以是一个AVI电影,一个WAV声音文件,Windows Media, 或是MP3音乐,或是一个MPEG电影。只要是DS filter能处理的文件格式,它都能对这个文件进行渲染。
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的源代码文件只包含了三个函数,包括标准的C/C++函数main, 它是程序的入口点。现在来重点分析它。
点击(此处)折叠或打开
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.
前面已经介绍了一些COM基础,现在进入到mian函数。先是定义几个指向COM对象的变量,再通过调用基本的Windows函数GetMediaFileName获得文件名,再调用CoInitializeEx函数初始化COM。
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.)
如果上述的操作返回成功。那么接着的是调用CoCreateInstance实例化Filter Graph Manager对象,并从这个调用中这个对象的IGraphBuilder接口—它提供了创建filter graph的方法。如果成功获得了IGraphBuilder接口(如果失败,则可能是DirectX或OS有问题),再调用两次QueryInterface来返回Filter Graph Manager的另外两个接口。第一次返回的是IMediaControl接口,它用来控制filter graph的执行状态。第二次返回的是IMediaEvent接口,它提供filter graph将自己的状态发送信号给DS应用程序的方法。在这种情况下,IMediaEvent可以用来追踪媒体文件的播放进程,它会暂停应用程序的执行直到播放结束。(对于OS高手来说:它可能是因为DS filter和DS应用程序在不同的线程中执行)
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.
很神奇的是,这里是只用一行代码就创建了整个filter graph。当IGraphBuilder方法的RenderFile被调用(以媒体文件名为参数)时,Filter Graph Manager对象会检查媒体文件的类型,然后确认相应的filter集—源,转换,和渲染—它们都会被添加到filter graph,并彼此连接。如果RenderFile返回成功,那么就表示DS找到了渲染源文件的filter路径。如果返回失败,则表示DS缺少播放这个媒体文件的filter,或是本身这个文件有问题。
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.
当filter graph创建后,调用IMediaControl接口的Run方法开始执行filter graph。尽管filter graph开始执行了,但是Run方法会立即返回,因为数据流会从源filter开始就在另一个单独的线程中执行。媒体文件开始播放了。如果媒体文件是一个电影,那么将会打开一个播放窗口。如果是一个声音文件,则不会打开播放窗口,但是会在扬声器中播放。Figure 3-1显示了AVI文件开播放。
Figure 3-1. DSRender playing the AVI file Sunset.avi
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.
应用程序肯定需要暂停正在播放的媒体文件。如果没有这个动作,那么应用程序在filter graph开始播放后就结束了,这肯定不行。这是为什么在播放时加入IMediaEvent接口的原因。以INFINITE作参数调用它的WaitForCompletion方法,将会使应用程序等待直到Filter Graph Manager知道媒体文件播放完了。在实际的应用中,可能并不需要使用INFINITE作参数来调用WaitForCompletion方法,因为这会导致如果一些事件导致停止了媒体文件的播放,那么应用程序会一直等下去。在本例中它还是能工作的,在本书的其它示例将会展示IMediaEvent接口更有效的使用方法。
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.
在播放完成后,需要调用IMediaControl对象的Stop方法来终止filter graph的执行。这个动作是必须的。因为filter graph并不会媒体文件的播放完成而停止自身。
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:
在filter graph停止后,将立即调用局部域的SaveFilterGraph函数,它接受两个参数:指向IGraphBuilder接口的指针和一个文件名。这个函数调用会生成一个GraphEdit可见的filter graph。
点击(此处)折叠或打开
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.
尽管用了一些前面没有讲的函数,但是这个函数还是很简单的。它先调用Windows函数StgCreateDocfile,打开输出文件,创建一个IStorage对象来表示文件。之后,再创建了一个IStream流对象。这个流用来提供输出文件数据路径。这个函数最有意思的是通过IGraphBuilder通过调用QueryInterface获得的Filter Graph Manager的IPersistStream接口,这个接口包含有能创建持久流对象的方法,这个对象可以写成文件并返回。当调用了IPersistStream的Save方法—以IStream对象的指针为参数—filter graph的数据结构将会被写到流中。
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. shows the MyGraph.GRF filter graph.
上述调用都成功后,则可以调用IStorage接口的Commit方法来将数据写到文件。此是,就针filter graph的快照输出到了文件。本程序使用的固定文件名。当DSRender结束后可以使用GraphEdit来查看这个文件,就可以看到DSRender创建的filter graph。这个filter graph是随着输入文件类型而不同的。Figure 3-2显示一个示例的filter graph。
Figure 3-2. GraphEdit showing the filter graph MyGraph.GRF created by DSRender
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….
DSRender是一个极其简陋的DS应用程序示例—没有装饰,没有额外的UI细节,仅仅只有媒体播。但是很多媒体文件都能在这个简单的应用程序中播放,因为DS的IGraphBuilder对象通过选择和连接合适的filter来创建一个功能化的filter graph处理了这些麻烦的工作。现在需要的是更往前走来自己创建filter graph处理这些烦琐的工作。
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.
DSBuild应用程序做了一些烦重的工作,包括有,创建了一个可以播放各种音频格式的音频播放器—几乎支持所有DS能支持的音频格式,包括AVI音轨和Windows Media电影。应用程序代码创建了一个Filter Graph Manager 对象并创建了两个filter:一个源filter(它指向文件)和一个音频渲染filter。然后,使用智能连接功能将源filter的输出pin,通过添加必要的转换filter连接到音频渲染filter的输入pin。当这个路径建立好后,这个filter graph就可以执行媒体文件的播放直到完成或停止。
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.cpp的源码包含有四个函数,有三个函数和DSRender.cpp中的几乎一样。另一个函数,GetPin的细节在“Locating Pins and GetPin’中详细分析。先的main函数开始。
点击(此处)折叠或打开
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.
Main函数的开始行和DSRender中的一样。先是通过COM调用实例化了一个Filter Graph Manager对象,然后通过QueryInterface的调用返回IMediaControl和IMediaEvent接口的指针,这是所有创建filter graph都必要的。在此时,调用了IGraphBuilder的一个新的方法,AddSourceFilter, 它以一个文件名为参数并返回一个指向IBaseFilter接口的指针,它被选择并实例化。IBaseFilter接口可以被所有DS filter使用。
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.)
接着使用CoCreateInstance创建一个音频渲染filter,它的类ID值为CLSID_DSoundRender,它将返回这个对象的IBaseFilter 接口。当这个filter创建成功后,通过调用IGraphBuilder的AddFilter方法将它加入到filter graph中。AddFilter有两个参数:第一个是要加入filter graph的filter的IBaseFilter接口指针,第二个参数是这个filter的字符串名称。
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.
现在filter graph中有了两个filter:一个指向文件的源filter和一个音频输出filter。它们可能需要通过转换filter连接在一起。这个转换filter可能会根据输入源文件的媒体类型不同而不同。为了不用通过检查源文件本身来确定转换filter的类型,这里直接使用DS的智能连接。
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.
首先,需要获得IPin接口—它被filter引用—包括源filter的输出pin和渲染filter的输入pin。这里使用GetPin函数来连接的这些pin的接口。当获得了这两个pin后,就可以调用IGraphBuilder的Connect方法。(Connect方法以两个pin为参数,如果返回成功,这个方法就通过某些中间filter将两个pin连接起来了)。如果调用失败,DS就不能创建源和渲染之间的路径,这可能是因为DS不支持源文件的媒体类型或是源文件中并没有音频数据。
As in DSRender, the application uses the IMediaControl interface’s Run method to begin execution of the filter graph, and the 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.
和DSRender一样,这个应用程序也是调用IMediaControl接口的Run方法来开始filter graph的执行,IMediaEvent的WaitForCompletion方法会暂停应用程序的执行直到媒体文件播放完毕。此时,调用Stop方法停止filter graph的执行。最后调用SaveGraphFile将filter graph写到文件,释放接口,应用程序结束。
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.
即使使用了手动添加,filter graph的创建和维护也还不是很困难。然而,如果这个应用程序没有智能连接那难度就会显著增加。
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:
GetPin函数用于定位filter上的输入和输出pin并返回可以控制这个pin的IPin接口。详细的代码实现如下:
点击(此处)折叠或打开
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.
IBaseFilter有一个成员函数,EnumPins,它用于返回IEnumPins接口,这个接口可以用来迭代遍历filter的所有pin的列表。每个IEnumPin列表的元素包含有一个IPin对象。当遍历这个pin列表时,每个pin都会被IPin::QueryDirection方法询问。如果这的方向匹配所要的, 那么这个IPin接口指针就成了函数的返回值—应当注意的是,有些filter有多个输入pin和输出pin,并且这些pin有不同的媒体类型,因此有时并不知道返回的IPin会有所有的情况下都能用。例如,如果GetPin是在一个数字视频filter中被调用,期望的也是一个数字视频的输出pin,最后会发现无法连接到视频渲染,因为输出pin对应的是音频输出,无法和视频对接。这个函数没有做这种处理。
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.
从DSRender和DSBuild可以后出,DS应用程序的创建并不是很难,DS对象的定义和接口也很容易使用。这些造就了DS的强大生命力。只用20来行的代码,就能创建一个完整的媒体播放器。这之后要做就只是添加用户接口。从这一点来说,图形用户接口的应用程序开发才是耗时耗力的。上面的两个例子都可以用在Windows应用程序中。