Source code download
I have been studying DirectShow for several days. I will write down my learning experience and hope to help others. Filter is actually a COM component, so you should have a little understanding of COM before learning to develop filter. The essence of the COM component is a C ++ object that implements the pure virtual pointer interface. Here is not much about com.
1. Configure the DirectShow development environment for VC
Whether developing a filter or developing a dshow application, you must configure the development environment to include the header files and dynamic libraries used by dshow. Select Options under the Tools menu. The Options dialog box is configured as follows:
Figure 1 Add a header file
Add the dynamic library file to the Project
Figure 2 Add a dynamic library
2. Create a project and filter entry functions
Create a project:
Generally, a common Win32 DLL project is used to create a filter. In addition, the filter project generally does not use MFC. In this case, the application uses the cocreateinstance function as the filter instance, and the filter and application work in the binary level. Alternatively, you can create a filter in the MFC application project.
Create a project in VC and select Win32 dynamic library, as shown in figure
Figure 3
Figure 4
This generates a simple DLL with only one dllmain entry function. Next I will add an entry function for this filter. The filter is a DLL-based COM component. Therefore, the following entry functions must be implemented for general filters:
Dllmain
Dllgetclassobject
Dllcanunloadnow
Dllregisterserver
Dllunregisterserver
First, define the export function:
There are two methods to export these functions. One is to use the export keyword _ declspec (dllexport) when defining the function, and the other is to use the module definition file when creating the DLL file. def. Using the export function keyword _ declspec (dllexport) to create mydll. dll is to define the function in the. h file as follows:
Extern "C" _ declspec (dllexport) bool dllregisterserver; and so on
To create a DLL using the. Def file, add a text file named mydll. Def to the project and add the following code to the file:
Library myfilter. Ax
Exports
Dllmain private
Dllgetclassobject private
Dllcanunloadnow private
Dllregisterserver private
Dllunregisterserver private
The library statement indicates that the def file belongs to the corresponding DLL. The exports statement lists the name of the function to be exported. We can add @ n after the export function in the. Def file, for example, Max @ 1, Min @ 2, to indicate the sequence number of the function to be exported. It can be used for explicit connection. After the DLL is compiled, open the DEBUG directory in the project, and you will also see the mydll. dll and mydll. Lib files.
Then we need to define the implementation of these functions. In fact, all these basic dshow classes have been completed for us, and all we have to do is use them, the implementation of the three most important functions is generally as follows:
Stdapi dllregisterserver ()
{
Return amoviedllregisterserver2 (true );
}
Stdapi dllunregisterserver ()
{
Return amoviedllregisterserver2 (false );
}
Extern "C" bool winapi dllentrypoint (hinstance, ulong, lpvoid );
Bool apientry dllmain (handle hmodule, DWORD dwreason, lpvoid lpreserved)
{
Return dllentrypoint (hinstance) (hmodule), dwreason, lpreserved );
}
Here, dllentrypoint is defined in C:/dx90sdk/samples/C ++/DirectShow/baseclasses/dllentry. cpp. If you are interested, let's take a look at its definition. The amoviedllregisterserver2 function is defined in the following C:/dx90sdk/samples/C ++/DirectShow/baseclasses/dllsetup. cpp file. You can see the specific implementation.
At this point, you may want to do some work, or set up your project environment. Otherwise, you may not be able to compile it, because you have used some of the basic classes, so you need to include the definition of your dshow base class and library file. First include: # include streams. h
Configure the name of the filter output and the connected lib file in the project-setting menu.
Figure 5
The dynamic libraries in the library modules are as follows:
C:/dx90sdk/samples/C ++/DirectShow/baseclasses/debug/strmbasd. lib msvcrtd. lib quartz. lib vfw32.lib winmm. lib kernel32.lib advapi32.lib version. lib largeint. lib user32.lib gdi32.lib comctl32.lib ole32.lib olepro32.lib oleaut32.lib UUID. lib
At this point, you compile it. It seems that it still does not work. It prompts that a global variable for implementing the COM interface is not defined, so don't worry. Next we will start to implement the filter COM interface.
3. How to Implement the Filter Factory object
We know that a filter is a COM component, so the implementation of its COM feature is actually implemented in its base class, such as the iunknown interface. After we directly derive our filter from the base class, it supports the COM interface, which is a COM component.
All COM components are encapsulated for Binary encapsulation, so the interfaces created are encapsulated. Therefore, each COM object has a class Object (also called a class factory object, which is also a COM object, to create COM components.
Next, let's take a look at the creation process of the COM component, which involves several functions:
When the client creates a COM component, it uses the underlying com api function cogetclassobject () to use the SCM service, this function requires SCM to bind a pointer to the Class Object of the COM component requested by the client. In fact, it loads the dll library in cogetclassobject, use the DLL export function dllgetclassobject (); dllgetclassobject returns the pointer of the COM component class Object Based on the COM component classid provided by the client. The creation of the following COM component has nothing to do with SCM.
The client uses the iclassfactory: createinstance method of the component class Object (class factory object) to create the COM component.
Here, a category factory template class is used as the category factory object of filter. Let's take a look at how the class factory works in dshow.
The class factory object is also a COM component. Originally, dllgetclassobject should be a function completed by ourselves, which has been completed in the DirectShow base class, so we don't need to worry about it. Its function is to find the class factory object in this DLL to see if there is a class factory object that meets the client request.
DLL declares a global factory class template array. When dllgetclassobject requests a factory class object, it searches for this array to see if there is a factory class object that matches clsid. When it finds a matched CLSID, it creates a class factory object and returns the class factory pointer to cogetclassobject. Then the client can call iclassfactory based on the class factory pointer returned :: the createinstance method creates a component, and the class factory creates a COM component based on the methods defined in the array.
The factory template contains the following variables: const wchar * m_name; // name
Const CLSID * m_clsid; // CLSID
Lpfnnewcomobject m_lpfnnew; // function to create an instance of the component
Lpfninitroutine m_lpfninit; // initialization function (optional)
Const amoviesetup_filter * m_pamoviesetup_filter; // set-up information (for filters)
The two function pointers m_lpfnnew and m_lpfninit use the following definition:
Typedef cunknown * (callback * lpfnnewcomobject) (lpunknown punkouter, hresult * phr );
Typedef void (callback * lpfninitroutine) (bool bloading, const CLSID * rclsid );
You can refer to the following method to define your factory objects:
Cunknown * winapi cmyfilter: createinstance (lpunknown punk, hresult * phr)
{
Cmyfilter * pfilter = new cmyfilter (name ("My filter"), punk, PHR );
If (pfilter = NULL)
{
* Phr = e_outofmemory;
}
Return pfilter;
}
You can declare your factory array as follows:
Cfactorytemplate g_templates [1] =
{
{
L "my filter", // name
& Clsid_myfilter, // CLSID
Cmyfilter: createinstance, // method to create an instance of mycomponent
Null, // initialization Function
& Sudinftee // set-up information (for filters)
}
};
Int g_ctemplates = sizeof (g_templates)/sizeof (g_templates [0]);
If you want to support multiple filters in this COM component, you can add more filters in this array.
4. How to implement your own Filter
Here we will talk about how to create your own filter. Here we take writing a ctransformfilter as an example:
1. Select a base class and declare your own class.
It is easy to create a filter. You only need to select a filter of different base classes based on your needs to derive your own filter, and it already supports the com feature.
Logically, it is crucial to select an appropriate filter base class before writing a filter. Therefore, you must have a good understanding of several filter base classes. In practice, the base class of the filter is not always cbasefilter. On the contrary, most of the data we write is the intermediate transmission filter (transform filter), so most of the base classes choose ctransformfilter and ctransinplacefilter. If we write the source filter, we can select csource as the base class; if it is Renderer Filter, we can select cbaserenderer or cbasevideorenderer.
In short, it is very important to select the filter base class. Of course, the filter base class is flexible and there is no absolute standard. The filters that can be implemented through ctransformfilter can also be implemented step by step from cbasefilter.
Next, I will give some suggestions on the selection of the filter base class based on my actual experience for your reference. First, you must specify the functions of the filter, that is, analyze the requirements of the filter project. Please try to maintain the singleness of the functions implemented by the filter. If necessary, you can break down the requirements by two (or more) filters with a single function to achieve the overall functional requirements.
Secondly, you should specify the position of the filter in the entire filter graph, the input data of the filter, the output data, the input pin, and the output pin. You can sketch the filter. Find out what is important at 01:10, which directly determines the filter of the "model" you are using. For example, if the filter has only one input pin and one output pin, and the same media type is entered, ctransinplacefilter is generally used as the filter base class. If the media type is different, ctransformfilter is generally used as the base class.
Furthermore, some special requirements for data transmission and processing are considered. For example, the input and output sample of the filter are not one-to-one, which generally requires data caching on the input pin, while the output pin uses a special thread for data processing. In this case, csource is the best choice for the filter base class (although this filter is not the source filter ). When the filter base class is selected, the pin base class is selected accordingly. Next, the code on the filter and pin is implemented. One thing to note is that from the software design perspective, you should separate your logic code from the filter code. Next, let's take a look at the implementation of the input pin. You need to implement all pure virtual functions of the base class, such as checkmediatype. In checkmediatype, you can check the media type to see if it is what you expect. Because most filters use the push mode to transmit data, the receive method is generally implemented on the input pin. Some base classes have implemented receive, while the filter class has a pure virtual function for users to reload data processing. In this case, you generally do not need to reload the receive method unless the implementation of the base class does not meet your actual requirements. If you have reloaded the receive method, the following functions, such as endofstream, beginflush, and endflush, will be reloaded at the same time. Let's take a look at the implementation of the output pin. In general, you need to implement all the pure virtual functions of the base class. In addition to checkmediatype for media type check, there is usually decidebuffersize to determine the memory size used by the sample, getmediatype provides supported media types.
Finally, let's take a look at the implementation of the filter class. First of all, you must implement all pure virtual functions of the base class. In addition, filter also implements createinstance to provide the com entry and nondelegatingqueryinterface to expose the supported interfaces. If we create a custom Input and Output pin, we usually need to overload the getpincount and getpin functions.
Here I mainly want to give an example. Therefore, the simple filter does not have a pin interface, but the filter in my demo has an out pin and an input pin. The filter class is defined as follows:
Class cmyfilter: Public ccritsec, public cbasefilter
{
Public:
Cmyfilter (tchar * pname, lpunknown punk, hresult * hr );
Virtual ~ Cmyfilter ();
Static cunknown * winapi createinstance (lpunknown punk, hresult * phr );
Cbasepin * getpin (int n );
Int getpincount ();
}
Note: Because the base class is a pure virtual base class, you must derive a pure virtual function from your filter, otherwise, the compiler will prompt that your derived class is also a pure virtual class. When you create this COM component object, the pure virtual class cannot create the object.
2. Generate a CLSID for the filter.
You can use guidgen or uuidgen to generate a 128-bit ID for your filter, and then use the define_guid macro to declare the CLSID of the filter in the filter header file; [myfilter. H]
// {1915c5c7-02aa-440f-890f-76d94c85aaf1}
Define_guid (clsid_myfilter,
0x1915c5c7, 0x2aa, 0x0000f, 0x89, 0xf, 0x76, 0xd9, 0x4c, 0x85, 0xaa, 0xf1 );
This clsid_myfilter is used in the category factory array and also used when registering the filter.
3. Simple implementation of the cmyfilter class
This class is purely intended for demonstration, so it is very simple. You can refer to my demo and the filter write function is quite complete.
Cmyfilter: cmyfilter (tchar * pname, lpunknown punk, hresult * hr)
: Cbasefilter (name ("My filter"), punk, this, clsid_myfilter)
{}
Cmyfilter ::~ Cmyfilter ()
{}
// Public method that returns a new instance.
Cunknown * winapi cmyfilter: createinstance (lpunknown punk, hresult * phr)
{
Cmyfilter * pfilter = new cmyfilter (name ("My filter"), punk, PHR );
If (pfilter = NULL)
{
* Phr = e_outofmemory;
}
Return pfilter;
}
Cbasepin * cmyfilter: getpin (int n)
{
Return NULL;
}
Int cmyfilter: getpincount ()
{
Return 0;
}
In this way, a filter is basically implemented, but this filter does not have a pin associated with it, but the basic process of implementing the filter is like this. As for the logic, for example, if you want to connect the filter and pin, and how the data stream flows, you have to check the SDK. You can write a filter framework according to the above steps.
Next we will summarize the things at least needed to write a filter.
1. Filter Implementation class
Here is the cmyfilter class. In this class, you can implement your own logical functions, including defining the features of your filter and configuring the pin interface for your filter.
2. the outgoing function of the COM component. Five global functions:
Dllmain // DLL entry function
Dllgetclassobject // obtain the class factory object of the COM Component
Dllcanunloadnow // whether the COM component can be uninstalled
Dllregisterserver // register the COM Component
Dllunregisterserver // uninstall the COM Component
The dllgetclassobject has been completed by the base class. You only need to complete three functions,
Dllmain, dllregisterserver, and dllunregisterserver.
3. COM component factory objects
The category factory object is used to generate the filter object. The template class is used to define a global Template Class Object array. The general format is as follows:
Cfactorytemplate g_templates [1] =
{
{
L "my filter", // name
& Clsid_myfilter, // CLSID
Cmyfilter: createinstance, // method to create an instance of mycomponent
Null, // initialization Function
& Sudinftee // set-up information (for filters)
}
};
Int g_ctemplates = sizeof (g_templates)/sizeof (g_templates [0]);
4. Information about your own filter and pin
These are global structure variables used to describe your filter and your defined pin, which will be used when you register the filter, as shown below: amoviesetup_filter describes a filter
Amoviesetup_pin description pin
Amoviesetup_mediatype description Data Type
The following code describes a filter with an output pin:
Static const wchar g_wszname [] = l "some filter ";
Amoviesetup_mediatype sudmediatypes [] = {
{& Mediatype_video, & mediasubtype_rgb24 },
{& Mediatype_video, & mediasubtype_rgb32 },
};
Amoviesetup_pin sudoutputpin = {
L "", // obsolete, not used.
False, // is this pin rendered?
True, // is it an output pin?
False, // can the filter create zero instances?
False, // does the filter create multiple instances?
& Guid_null, // obsolete.
Null, // obsolete.
2, // number of media types.
Sudmediatypes // pointer to media types.
};
Amoviesetup_filter sudfilterreg = {
& Clsid_somefilter, // filter clsid.
G_wszname, // Filter Name.
Merit_normal, // merit.
1, // Number of Pin types.
& Sudoutputpin // pointer to pin information.
};
Finally, if you still cannot debug it, check whether you include the following header file: # include <streams. h>
# Include <initguid. h>
# Include <tchar. h>
# Include <stdio. h>