IkMijn pagina voor
prettig werken op de PC
www.pcpret.nl
Homepage Stuur bericht Gastenboek Website Blog Eindhoven

Software development, DirectX and DirectShow

Related information I wrote: Introduction.
DirectX offers a lot of useful multimedia functionality that can be used within own written programs that makes use of graphics, audio and/or video. To get access to all media capabilities of the Windows computer, interface knowledge of specific Microsoft software such as DirectX is really needed. E.g. the FM radio and TV project, look here for more information, uses DirectShow. The nice thing is that DirectX exists for many years already. So a lot of software have been written and a little seems to be changed. With DirectX 8.1 and DirectX 9 there seems to be so minor and major changes. However, a little bit difficult to find out what is really changed or new. The VMR functionality, esspecially VMR9, is very usable for mixing and adjusting video signals. So that functionality is interesting to use.
DirectX is a collection of several parts: Direct3D, DirectInput, DirectMusic, DirectPlay, DirectShow and DirectSound. And this is not all, DirectX has also diagnostic functions such as DXErr and getDXVer. This chapter gives some inside information on the DirectShow part because I choosed that subject as training project to learn C#, DirectX and COM.

Software
The normal way to use DirectX and DirectShow functionality is to use the C++ oriented DirectX SDK with the corresponding header files and libraries. The most interesting program to start with is Amcap. There are some versions floating around. When Using Delphi or C# (or just .Net) a special interface is needed to use the DirectX functionality. Knowledge about Windows programming and C++ will be an advantage.
I started with DSPack (see
Prodigy) for Delphi. A great package, only I did not have the DirectX knowledge yet. The example I started with, did not provide TV tuner support and could not switch pins.
The next step was to get insight information on DirectX. How? By looking around on the internet, getting the DirectX SDK, getting the Windows DDK. The DDK is needed in some cases because some header files and libraries are missing that seems to be needed for most of the SDK samples.
Than I came across The Code Project. On this site a lot of general information, programming information and examples on DirectX with C++, C# and Visual Basic can be found. An interesting article for me was the DirectX.Capture project written by Brian Low (2003) that gives insight information on DirectShow. Keep in mind that it is just an example. The code seems to be okay, however problems may arise. Sometimes it is just a programming error (mostly made by myself), sometimes it seems to be a mistery. If you start digging around in de DirectX.Capture example, then it might be useful to look for a slightly modified version of the DirectX.Capture example. The code has some additional functionality, also the code seems to be more stable. The TV sound (captured via a PCI TV card) worked right away, with DShowNet, it did not work. A nice extension is that the configuration can be saved. It will save you a lot of clicking when testing and using the software. And of course, I had already something written like that (not as nice and complete as that) ...

Directshow libraries
Originally the C# example DirectX.Capture came with DShowNet. The use of DShowNet is free, no restrictions! Thanks to .Net DShowNet can be used for any .Net language. This library contains only the more general usable DirectShow interfaces. These interfaces can be more or less be derived from the C header files in the DirectX SDK. Additional information how to use it can quite often be found on MSDN or internet. Sometimes a programming example gives more information. However, quite often the information is very compact, incomplete or just missing.
Nowadays there is, for .Net (and C#), also DirectShowLib. That library, Directshownet, is subject to the LGPL license. DirectShowLib seems to contain all DirectX 9 interfaces, most of them untested. I used some of the untested interfaces, and partly, they worked okay. I found out that the IAMWstDecoder interface needs a correction. With respect to the IAMWstDecoder interface, the GetOutputFormat function does not work and I could not figure out yet, what goes wrong. The GetCurrentPage function works, only the declaration of the output parameter needs to be modified. It needs a call to Marshalas. I would expect that most of the interfaces that return data via an output parameter might fail, those interfaces will probably need one or more modifications.

Practicing Directshow
Starting with a real DirectShow example is the easiest way to start. The DirectX.Capture example mentioned before, is not an easy start that is a big challange. Because I knew how to work with C++ and I am quite familiar with the C++ counterpart Amcap, so there was a good chance to understand how to use DirectX, DirectShow and C# in the real world. Furthermore I had many wishes, such as:
- Easy selection of broadcast stations on names
- Use the FM Radio on the Hauppauge PVR150-MCE TV-Card
- Mpeg video capturing with Hauppauge PVR150-MCE TV-Card
- Wav, MP3 audio capturing
- Use of the Microsoft remote control
- Teletext (WST)
- User VMR, VMR9
- Get acquinted with BDA
- Easy configuration and usable on various types of capture devices

When starting such project, it is important to have wishes so it becomes a game. Get a wish completed and the game goes to a higher level ... But the real work starts with the boring stuff: Compiling the DirectX.Capture project with Visual Studio. Sometimes it just works (not for me). Most of the time an library path must be set or Visual Studio setting must be changed. Than you push the Debug-Start button to start the program.
There is a good chance the program works, than you have to figure how the program must be used. After selecting the video device, the audio device and the preview I usually got a picture but no audible sound ... Also I noticed that the program was slow and the TV picture was moving slow too. So the list of whisher becames a little longer: program must become more speedy and the video quality should increase.
Soon I noticed that the program was very unstable, a lot of program errors occured and than the program seems to become unusable. Sometimes the program crashed, most of the time the error seems to be catched. This is the way I discovered the nice and dangerous use of catching errors that are thrown somewhere in the program.
Than the real work started, become acquinted with all kind of C# (.Net) features and object oriented programming. Maybe sometimes, there is a wish to go back to the old reliable C++ because reading this code is not an easy job. This program shows how object oriented programming can be used the right (and the wrong) way. My impression the job can be done better so take your chance to improve it. Keep in mind to take a look at the DirectX.Capture page on The Code Project or the pages listed at the top of this webpage. These pages shows questions from other developers and you can post also your own questions.

What I learned from DirectX.Capture and DirectX is that the description of most DirectX functions is bad or incomplete. Also most examples are in C++ so without knowledge of C++ it is difficult to translate to the C# way of programming. On the other hand, MSDN does explain a lot of subjects. Sometimes difficult to find or to find back, so make notes!
Error handling is very important. The DirectX.Capture shows how to handle that good and bad. Than of course DirectX is also a little bit guilty. When a RenderStream fails, it is unknown if possible allocated resources have been released properly. Keep in mind that when an error occurs, than that will be the best moment to release possible allocated resources that might have caused the error. Or just release those allocated resources (and reallocate if needed) so the program comes back in a usable state when a thrown is done. The main disadvantage of this program is that that does not happen. When a thrown occurs, the program does not know what to do. Mostly a complete reset is needed (choose a new video or audio device) or the program just crashes. If a thrown is done, than the release of previous allocated resources is not possible anymore because the information needed is just gone. At a certain moment I discovered that most errors occur when mediaControl.Run() was called in the function StartPreviewIfNeeded(). From that moment on I used that function as reference as return point of a thrown for a graph build that failed for whatever reason. That was also the moment I noticed that the DirectX.Capture shows more stability. Important is to put that function in a try - catch section and do the appropriate error handling if not done yet. But keep in mind resources needs to be released as early as possible, so better before a thrown that after it. Keep in mind to release possible added filters but still unconnected, eg a compression or a file writer filter. These unused resources must be released too. I got fatal errors when keeping the filters in the graph, especially when I went from avi->asf file saving when changing the video render mode. A question mark maybe on the compression filter, the DirectX.Capture authors status that a removal is not needed, however, I got fatal errors when keeping it in (when changing the video render mode too). The advantage of such filters in the graph as long as possible is that the settings are not lost. A compression filter might be needed in the Avi leg of the graph. If the property interface of all filters would be known, than a program could use that property interface to change the settings itselfs. Unfortunately most property interfaces, e.g. the Hauppauge Mpeg2 encoder, the Hauppauge file writer and the DV Avi compressor can be accessed via GUI interface only. Maybe there is a different way, but I did not find it (yet). For the Asf file writer there seems to be a solution and my articles at The Code Project shows a possible implementation.

For getting a better picture quality the use of a specific de-interlace filter might be a solution. The major improvement I made to the DirectX.Capture program is the audio rendering part. The original implementation supports wired TV-Card audio connections only. This means that the audio capture device is usually the sound card in the computer. It also means that audio is played "always". Problems occur when a TV-card, such as the Hauppauge PVR150-MCE, that does support audio capturing via the PCI bus. Than specific code needs to be added to render audio so audible sound is played. More difficult it becomes when a TV-Card does not have its own audio capture device. This is the case with the new december 2005 driver of the Hauppauge PVR150-MCE driver ... Than more code changes are needed to get the audio device handled properly. I added an option that enforce a check on the video device to find out if it supports audio. If so than the result becomes the audio device filter.
Besides these programming challenges there is also things to repair. Quite often the sound connection is lost, I discovered that this have quite often to do with the video crossbar device. An option is added to look an audio pin which is related to a video pin. An effective solution is to release the audio and video sources and the property pages upon use.
Another interesting feature is the Video Standard. Most software seems to be default to NTSC, but in my country the Pal video format is the common video format. As result the video is displayed partly and distorted. Also the capture size of 720x576 can not be selected. Thanks to a different project ( Change Video Signal Format with DirectX library), there is an easy solution for changing the video format. The video format affects the video frame size (for Pal usually 720x576, while for NTSC 720x480 is common). But there is more, the country code should have also be set to the proper value. The country code quite often affects the Video Standards that can be chosen and in some cases it also affects the TV audio and the TV broadcast selection.

Graphedt, GraphStudio: Directshow graph tools
Very nice utility is Graphedt.exe (a Directshow graph tool in Microsoft's DirectX SDK). With Graphedt it is possible to make a working design that could be tested in advance. There is one disadvantage, it uses the filters that are on the computer, so specific configurations can only be tested on dedicated computers.

> Graphedt is ideal to find out which components or filters fits together and which pins can/must be used? A very nice thing is that, when there are sufficient system resources, the design can also be tested! The main disadvantage is probably that it does not provide real code.
The biggest advantage of using graphedt is to find out how filters needs to be handled. So it is the first step to check whether RenderStream can do the job itselfs. Or to find out if one or more filters needs to be added in advance, before RenderStream is called. Or to find out that some filters needs to be added and/or connected separately. So most of the time RenderStream tries to find suitable filters and will find them itselfs. Sometimes RenderStream needs some help, than adding one filter is quite often enough, sometimes not. In some cases the filter is already there and a findinterface is sufficient.

For using Graphedt the proppage.dll file (can be found together with grahpedt.exe in the DirectX SDK) needs to be registered. Registering goes via regsvr32.exe, a normal Windows tool: 'regsvr32 proppage.dll'. Unregistering proppage.dll can be done with regsvr32 also: 'regsvr32 proppage.dll /u'. The regsvr32 command can be excuted via a command line (cmd.exe). For Windows Vista and Windows 7 the cmd.exe needs to be started as administrator (e.g. by clicking on the filename with the right mouse button).

Another interesting Directshow graph tool is the opensource GPL tool GraphStudio. See Blog Monogram-GraphStudio for more information. This tool looks a little bit more user friendly then the graphedt tool.

Windows media format SDK
The use of the ASF (=Advanced Systems Format) file format means also that you have to become familiar with the Windows media format SDK. Mainly because it describes the interfaces that are needed to use the functionality in a more user friendly way. Interfaces that will be used are: IWMProfile, IWMProfile2, IWMProfileManager, IWMProfileManager2, IWMProfileManagerLanguage, IWMStreamConfig, IWMStreamList, IWMWriter, INSSBuffer, IWMInputMediaProps, IWMMediaProps, IWMWriterAdvanced, IWMWriterAdvanced2, IWMWriterSink, IServiceProvider.
The most important information to retrieve are the profiles that define the audio and/or the video format of an ASF file. The profiles can be selected via Guid, this method seems to be phased out. Another way is to select on the name of the profile or loading a file containing the profile. For the user it does not really matter how it is implemented. What counts is the user friendlyness. I would assume that selecting a format from a list will be very user friendly. FM Radio version 1.2 There are some things that might be interesting: instead of Asf (file extension) also the file extensions wma (audio) and wmv (video and/or audio) can be used. For audio it is important to select a format without any video. For video it is important to select a format with video. But there is more, if video is chosen and the format does not support audio, then also no audio might be captured. How to find out if an Asf or windows media format supports audio and/or video? Well, that is possible by getting the IWMProfile information corresponding with the profile. Then get the stream information (via IWMStreamConfig) which provides you the Guid of the stream type, e.g. video, audio or something else. The types video and audio should be taken into account, of course ...

IAMVfwCompressDialogs as coding example for making an interface
I came across the IAMVfwCompressDialogs interface when I was looking for a solution to get a possible property page for a video compressor. There are two methods to find a property page. First, the ISpecifyPropertyPages interface can be used, e.g. 'DV Video Encoder'. Second, the IAMVfwCompressDialogs interface can be used, e.g. 'Microsoft Video 1'. The second interface seems to be used older filters. Than I found a note somewhere in the code that the functionality in the IAMVfwCompressDialogs interface does not seem to work properly, even in C++. Well interesing, I like to dig and learn, so I tried to use it in C#. To use an interface, the interface must be defined first. Happily there are libraries who did the job for me.

  1. DShowNET (can be found at www.thecodeproject.com, declaration of the GetState() function within the IAMVfwCompressDialogs interface.
    // Calls ICGetState and gives you the result
    int GetState(
       [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] byte[] pState, ref int pcbState );
    
  2. DirectShowLib (can be found at www.sourceforge.net), declaration of the GetState() function within the IAMVfwCompressDialogs interface:
    [PreserveSig]
    int GetState([In] IntPtr pState, [In, Out] ref int pcbState);
    
Both interfaces will work, but it might be good to look a little bit further when choosing the final interface. Why, the DShowNET interface looks more complicated but delivers little code when it is used. This because a managed byte[] buffer can be used. The coding for using GetState() could be (piece of code out VfwCompressorPropertyPage.cs from the DirectX.Capture class example):
// vfwCompressDialogs points to the property page pointer.
// IAMVfwCompressDialogs vfwCompressDialogs;
byte[] data = null;
int size = 0;

// interface for GetState is:
// int GetState([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] byte[] pState, ref int pcbState);
int hr = vfwCompressDialogs.GetState(null, ref size);
if((hr == 0)&&(size > 0))
{
   data = new byte[size];
   hr = vfwCompressDialogs.GetState(data, ref size);
   if(hr != 0)
   {
       data = null;
   }
}
When using the DirectShowLib version, coding becomes more complicated because unmanged data needs to be used and also data needs to be moved from unmanaged to managed data.
// vfwCompressDialogs points to the property page pointer.
// IAMVfwCompressDialogs vfwCompressDialogs;
// Get compress dialog data
byte[] data = null;
int size = 0;

int hr = vfwCompressDialogs.GetState(IntPtr.Zero, ref size);
if((hr == 0)&&(size > 0))
{
    data = new byte[size];
    int sizeIntPtr = (size + 3)/ 4; // size in integer, rounded upwards using size in bytes
    IntPtr IntPtrData = Marshal.AllocCoTaskMem(sizeIntPtr);

    hr = vfwCompressDialogs.GetState(IntPtrData, ref size);
    if(hr >= 0)
    {
        Marshal.Copy(IntPtrData, data, 0, sizeIntPtr);
    }
    Marshal.FreeCoTaskMem(IntPtrData);
    if(hr != 0)
   {
       data = null;
   }
}
The DShowNET coding version is easy to understand and is also following the ASAP principle: As Simple As Possible. The DirectShowLib coding version is more complicated and there is more chance that unmanaged code is treated incorrectly. It is quite common to forget releasing of allocated memory. In this scenario it is clear and safe to release the allocated memory. The use of Marshal.ReleaseComObject() has usually more unexpected side-effects ...

Interesting links

Back to the beginning? Click here.


Back to the Software Development page,
the Homepage or the Hobby activities page.


Contact me? Go back to/look at the Software Development page.
Date: February 28, 2011