Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / DirectX

DirectShow: TV Fine-tuning using IKsPropertySet in C#

4.70/5 (21 votes)
31 Mar 2009CPOL13 min read 12   13.1K  
Enhancements to the DirectX.Capture class for TV fine-tuning using IKsPropertySet.

Sample image

Introduction

This article is a follow-up of my previous articles Audio File Saving for the DirectX.Capture Class and Video File Saving in Windows Media Video Format for the DirectX.Capture Class Library. Those articles describe how to do file saving for captured audio and video. This article will show a solution for getting a specific broadcast station by the selection of a specific station name or just entering the broadcast frequency to get the audio and video to be captured. Associated with this article, some extra features such as the VMR9, de-interlacing, and FM Radio are described.

Background

The article that gave me the basic idea was DirectShow - Fine TV Tuning using IKsPropertySet written by Liaqat Fayyaz.

When I started this project, I thought, "What is happening here?" Step-by-step, I discovered what was happening, and I discovered that translating about 70 lines of C code into working C# code was not that easy! I had to understand the meaning of a lot of DirectShow-specific code. It was quite difficult to find usable information. The Liaqat Fayyaz article helped me find the proper terms, and then, with much thought, I discovered how to use the unmanaged data in C# without getting errors. Finally, it took 300 lines of C# code to make TV fine-tuning work. The count of 300 lines is because there is also some "invisible C code" needed that is in some DirectX SDK include files. To understand everything, I had to visit MSDN many times.

The normal way of selecting a TV channel or a TV broadcast station is to choose a channel number between 1 and 368 (for the Netherlands, Europe). These channel numbers are based on a pre-defined frequency table, with frequencies between 45 and 863 MHz. The exact frequencies depend on the country, video standard (PAL, NTSC, SECAM), and the TV tuner. Via the IAMTuner interface and the put_Channel method, the desired TV channel can be chosen.

Still, there is the problem that not every TV broadcast station can be tuned. This is the main reason why I prefer the solution of just choosing one of the frequencies my cable provider offers me. Simply enter the frequency, do a little fine-tuning, and then view the TV program. Happily, the IKsPropertySet interface of the TV tuner object offers this solution.

Using the Code

To understand the C# code, you must understand the original C code that I used as a starting point. This code can be found in Liaqat Fayyaz's article DirectShow - Fine TV Tuning using IKsPropertySet. Based on this article, I wrote a C# solution. We'll start with some original C code and a short explanation. Looking at the next macro, you will probably think, "Wow, what is happening here?" Well, that was at least what I thought.

C++
#define INSTANCEDATA_OF_PROPERTY_PTR(x) ((PKSPROPERTY((x))) + 1)

This is a pointer to the actual data that is used as input and/or returned as output.

C++
#define INSTANCEDATA_OF_PROPERTY_SIZE(x) (sizeof((x)) - sizeof(KSPROPERTY))

This is the size of the actual property data structure that is used for input and output.

C++
hr = m_pTvtuner->QueryInterface(IID_IKsPropertySet, (void**)&m_pKSProp);

QueryInterface does a query for the interface pointer of an object, in this case the TV tuner object. Without having such an interface, there is nothing to do.

C++
KSPROPERTY_TUNER_MODE_CAPS_S ModeCaps;
KSPROPERTY_TUNER_FREQUENCY_S Frequency;
memset(&ModeCaps,0,sizeof(KSPROPERTY_TUNER_MODE_CAPS_S));
memset(&Frequency,0,sizeof(KSPROPERTY_TUNER_FREQUENCY_S));

This shows the property data structures and the memory allocation for the TUNER_MODE_CAPS_S and TUNER_FREQUENCY_S properties used in the SetFrequency() function. The TUNER_MODE_CAPS_S property data structure provides the capabilities of the TV tuner devices. Important information that is required includes the minimum frequency and the maximum frequency that can be tuned. The TUNER_FREQUENCY_S property data structure is used for setting the frequency.

C++
hr = m_pKSProp->QuerySupported(PROPSETID_TUNER, KSPROPERTY_TUNER_MODE_CAPS,
                               &dwSupported);

QuerySupported checks if it is possible to use the get or set methods for a specific property.

C++
if(SUCCEEDED(hr) && dwSupported&KSPROPERTY_SUPPORT_GET)
{
    DWORD cbBytes=0;
    hr = m_pKSProp->Get(PROPSETID_TUNER,KSPROPERTY_TUNER_MODE_CAPS,
        INSTANCEDATA_OF_PROPERTY_PTR(&ModeCaps),
        INSTANCEDATA_OF_PROPERTY_SIZE(ModeCaps),
        &ModeCaps, sizeof(ModeCaps), &cbBytes);  
}
else
    return E_FAIL;

The TUNER_MODE_CAPS_S property data is used to initialize the tuning flags. The new frequency is copied into the TUNER_FREQUECY_S property data.

C++
Frequency.Frequency=Freq;
if(ModeCaps.Strategy==KS_TUNER_STRATEGY_DRIVER_TUNES)
    Frequency.TuningFlags=KS_TUNER_TUNING_FINE;
else
    Frequency.TuningFlags=KS_TUNER_TUNING_EXACT;

The new frequency is validated. If the frequency is within the range, the frequency change will be sent to the TV tuner object via the set method.

C++
if(Freq>=ModeCaps.MinFrequency && Freq<=ModeCaps.MaxFrequency)
{
    hr = m_pKSProp->Set(PROPSETID_TUNER,
        KSPROPERTY_TUNER_FREQUENCY,
        INSTANCEDATA_OF_PROPERTY_PTR(&Frequency),
        INSTANCEDATA_OF_PROPERTY_SIZE(Frequency),
        &Frequency, sizeof(Frequency));
    if(FAILED(hr))
        return E_FAIL;  
}
else
    return E_FAIL;

If everything goes as expected, the new TV frequency changes and the TV channel shows up. If the previous code is not complete, the following code must be taken into account too! The code shown comes from strmif.h, ks.h, and ksmedia.h in the DirectX SDK.

C++
MIDL_INTERFACE("31EFAC30-515C-11d0-A9AA-00AA0061BE93")
IKsPropertySet : public IUnknown
{
public:
    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Set( 
            /* [in] */ REFGUID guidPropSet,
            /* [in] */ DWORD dwPropID,
            /* [size_is][in] */ LPVOID pInstanceData,
            /* [in] */ DWORD cbInstanceData,
            /* [size_is][in] */ LPVOID pPropData,
            /* [in] */ DWORD cbPropData) = 0;
        
    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Get( 
            /* [in] */ REFGUID guidPropSet,
            /* [in] */ DWORD dwPropID,
            /* [size_is][in] */ LPVOID pInstanceData,
            /* [in] */ DWORD cbInstanceData,
            /* [size_is][out] */ LPVOID pPropData,
            /* [in] */ DWORD cbPropData,
            /* [out] */ DWORD *pcbReturned) = 0;
        
    virtual HRESULT STDMETHODCALLTYPE QuerySupported( 
            /* [in] */ REFGUID guidPropSet,
            /* [in] */ DWORD dwPropID,
            /* [out] */ DWORD *pTypeSupport) = 0;
};

typedef struct 
{
    GUID  Set;
    ULONG  Id;
    ULONG  Flags;
}

KSIDENTIFIER;

typedef KSIDENTIFIER KSPROPERTY;

typedef struct 
{
    KSPROPERTY Property;
    ULONG  Frequency;                   // Hz
    ULONG  LastFrequency;               // Hz (last known good)
    ULONG  TuningFlags;                 // KS_TUNER_TUNING_FLAGS
    ULONG  VideoSubChannel;             // DSS
    ULONG  AudioSubChannel;             // DSS
    ULONG  Channel;                     // VBI decoders
    ULONG  Country;                     // VBI decoders
} 

KSPROPERTY_TUNER_FREQUENCY_S, *PKSPROPERTY_TUNER_FREQUENCY_S;

This code shows the interface and some data structures. Maybe, you will not believe this, but the most difficult part of the whole C-to-C# translation was to get a usable data structure.

Now the Real Stuff: Go from C to C#

QueryInterface() is, in C#, a typecast of the IAMTVTuner object to the IKsPropertySet interface pointer. QuerySupported() is called to check if the property data can be read and written. In the C# version, the TUNER_MODE_CAPS_S property data structure will not be used, mainly because this keeps the code simple. Instead of that, the minimum and maximum frequency are set to fixed values. It is up to you how to initialize. For NTSC countries, the range is normally 45 to 801MHz, and for PAL countries, the range is normally 45 to 863MHz. Keep in mind that TV tuners might have a different range.

The TUNER_FREQUENCY_S property data is read first, so it can be used for writing too. The main reason is that this way, all attributes will be initialized. The tuning flag in the TUNER_FREQUENCY_S property data attribute TuningFlags will be set to KS_TUNER_TUNING_EXACT because this is what I want to do: change the tuning frequency into the specified value. The new frequency is stored in the TUNER_FREQUENCY_S property data attribute Frequency.

After this, the TUNER_FREQUENCY_S property data is written with the set method. To make the code more correct, I added a check for using get and set via QuerySupported. There is little chance that the functionality is not supported. Putting everything together, the following solution came up:

C#
/// <summary />
/// Set broadcast TV tuning frequency using the IKsPropertySet interface.
/// </summary />

public int SetFrequency(int Freq)
{ 
    int hr;
    IKsPropertySet pKs = tvTuner as IKsPropertySet;
    KSPropertySupport dwSupported = new KSPropertySupport();
    DshowError errorCode = DshowError.VFW_NO_ERROR;
        
    // Use IKsPropertySet interface (interface for Vfw like property
    // window) for getting/setting tuner specific information.
    // Check first if the Property is supported.
    if(pKs == null)
    {
        errorCode = DshowError.VFW_E_NO_INTERFACE;
        return (int)errorCode;
    }
    // Use IKsPropertySet interface (interface for Vfw like property
    // window) for getting and setting tuner specific information
    // like the real broadcast frequency.
    hr = pKs.QuerySupported(
        PROPSETID_TUNER, 
        (int)KSPROPERTY_TUNER.TUNER_FREQUENCY,
        out dwSupported);
    if(hr == 0)
    {
        if( ((dwSupported & KSPropertySupport.Get)== 
              KSPropertySupport.Get)&&
            ((dwSupported & KSPropertySupport.Set)== KSPropertySupport.Set)&
            (Freq >= this.minFrequency && Freq <= 
            this.maxFrequency) )
        {
            // Create and prepare data structures
            KSPROPERTY_TUNER_FREQUENCY_S Frequency = 
                new KSPROPERTY_TUNER_FREQUENCY_S();
            IntPtr freqData = Marshal.AllocCoTaskMem(
                Marshal.SizeOf(Frequency));
            IntPtr instData = Marshal.AllocCoTaskMem(
                Marshal.SizeOf(Frequency.Instance));
            int cbBytes = 0;

            // Convert the data
            Marshal.StructureToPtr(Frequency, freqData, true);
            Marshal.StructureToPtr(Frequency.Instance, instData, true);

            hr = pKs.Get(
                PROPSETID_TUNER,
                (int)KSPROPERTY_TUNER.TUNER_FREQUENCY,
                instData,
                Marshal.SizeOf(Frequency.Instance),
                freqData,
                Marshal.SizeOf(Frequency),
                out cbBytes);
            if(hr == 0)
            {
                // Specify the TV broadcast frequency and tuning flag
                Frequency.Instance.Frequency = Freq;
                Frequency.Instance.TuningFlags =
                    (int)KS_TUNER_TUNING_FLAGS.TUNING_EXACT;

                // Convert the data
                Marshal.StructureToPtr(Frequency, freqData, true);
                Marshal.StructureToPtr(Frequency.Instance, instData, true);

                // Now change the broadcast frequency
                hr = pKs.Set(
                    PROPSETID_TUNER,
                    (int)KSPROPERTY_TUNER.TUNER_FREQUENCY,
                    instData,
                    Marshal.SizeOf(Frequency.Instance),
                    freqData,
                    Marshal.SizeOf(Frequency));
                if(hr < 0)
                {
                    errorCode = (DshowError)hr;
                }
            } 
            else
            {
                errorCode = (DshowError)hr;
            }
            if(freqData != IntPtr.Zero)
            {
                Marshal.FreeCoTaskMem(freqData);
            }
            if(instData != IntPtr.Zero)
            {
                Marshal.FreeCoTaskMem(instData);
            }
        }
    } 
    else
    {   // QuerySupported
        errorCode = (DshowError)hr;
    }

    return (int)errorCode;
}

KSPROPERTY_TUNER_FREQUENCY

The most difficult part for me was to make a data structure that did not cause an error when using the get and set methods. The get and set methods need parameters pointing to the tuner data. In C, the get/set interface needs a pointer (done via the two macros mentioned before) to the whole data structure KSPROPERTY_TUNER_FREQUENCY_S, the part with only the tuner-specific attributes (starting from the attribute Frequency), and the size of the data. C# does not know pointers, so I needed a different solution.

The first step was to translate the KSPROPERTY_TUNER_FREQUENCY_S structure into C# using [StructLayout( .... How do we access the tuner-specific attributes in a C# friendly way? Well, I decided to put the tuner-specific attributes in a new TUNER_FREQUENCY-specific data structure and create another structure with the KSPROPERTY structure and TUNER_FREQUENCY. Still, the code did not work as expected. For some unknown reason, I decided to change the size of the data structure by adding dummy attributes. Now, the C# code came to life.

C#
/// <summary />
/// KSPROPERTY tuner frequency data structure
/// </summary />
[StructLayout(LayoutKind.Sequential)]
public struct KSPROPERTY_TUNERFREQUENCY
{
    /// <summary /> Hz </summary />
    [MarshalAs(UnmanagedType.U4)]
    public int  Frequency;                
    /// <summary /> Hz (last known good) </summary />
    [MarshalAs(UnmanagedType.U4)]
    public int  LastFrequency;          
    /// <summary /> KS_TUNER_TUNING_FLAGS </summary />
    [MarshalAs(UnmanagedType.U4)]
    public int  TuningFlags;            
    /// <summary /> DSS </summary />
    [MarshalAs(UnmanagedType.U4)]
    public int  VideoSubChannel;        
    /// <summary /> DSS </summary />
    [MarshalAs(UnmanagedType.U4)]
    public int  AudioSubChannel;
    /// <summary /> Channel number </summary />
    [MarshalAs(UnmanagedType.U4)]
    public int  Channel;                
    /// <summary /> Country number </summary />
    [MarshalAs(UnmanagedType.U4)]
    public int  Country;                
    /// <summary /> Undocumented or error ... </summary />
    [MarshalAs(UnmanagedType.U4)]
    public int  Dummy;                  
    // Dummy added to get a successful return of the Get, Set 
    // function
}

/// <summary />
/// KSPROPERTY tuner frequency structure including the tuner 
/// frequency data structure.
/// Size is 6 + 7 (+ 1 dummy) ints
/// </summary />
[StructLayout(LayoutKind.Sequential)]
public struct KSPROPERTY_TUNER_FREQUENCY_S
{
    /// <summary /> Property Guid </summary />
    public KSPROPERTY Property;            
    /// <summary /> Tuner frequency data structure 
    /// </summary />
    public KSPROPERTY_TUNERFREQUENCY Instance;    
}
// KSPROPERTY_TUNER_FREQUENCY_S, *PKSPROPERTY_TUNER_FREQUENCY_S;

Additional Features in this Code Example

Video De-interlacing

Quite often, the video preview quality looks bad. Using a de-interlace filter may improve the preview quality dramatically. The main reason I added the de-interlace filter has nothing to do with the preview quality. I added this filter because it enabled a preview for my Hauppauge PVR150 TV-card. Usually, I got a black screen, so no video was displayed. With this filter in, however, video seems to be playing right away.

I chose the Alpary filter because it can be used freely. Other filters, such as the ffdshow filter at Sourceforge.net, might be usable too. But I have not tested any. The function FindDeinterlaceFilter() scans filters.LegacyFilters to find the specified filter. It is easy to specify a different filter.

C#
string filterName = "Alparysoft Deinterlace Filter";
Filter DeInterlace = null;

for (int i = 0; i < this.filters.LegacyFilters.Count; i++)
{
    if (filters.LegacyFilters[i].Name.StartsWith(filterName))
    {
        this.capture.DeInterlace = filters.LegacyFilters[i];
        return true;
    }
}

This filter is added to the graph just before calling RenderStream() to render the video. If the filter is in, RenderStream() will usually add this filter automatically. In some cases, the de-interlace filter will not be added to the graph. In those cases, extra code is needed to add the de-interlace filter explicitly. The preview quality will be better when VMR9 (Video Mixing Renderer 9) is used, even if no extra de-interlace filter is used. Interestingly, VMR9 offers de-interlacing itself (IVMRDeinterlaceControl9). It can be used via the VMR9 property page or via a software control.

Video De-interlacing and VMR9

I added an option to the program to use VMR9 more easily. To initialize the proper video renderer, the function InitVideoRenderer should be called upon rendering a video.

C#
#if DSHOWNET
/// <summary />
/// CLSID_VideoRenderer
/// </summary />
[ComImport, Guid("70e102b0-5556-11ce-97c0-00aa0055595a")]
public class VideoRenderer
{
}
#endif

/// <summary />
/// Use VMR9 flag, if false use the video renderer instead
/// </summary />
private bool useVMR9 = false;

private IBaseFilter videoRendererFilter = null;
/// <summary />
/// Check if VMR9 should be used
/// </summary />
public bool UseVMR9
{
    get { return this.useVMR9; }
    set    { this.useVMR9 = value; }
}

private bool InitVideoRenderer()
{
    if(this.useVMR9)
    {
        this.videoRendererFilter = (IBaseFilter)new VideoMixingRenderer9();
    }
    else
    {
        this.videoRendererFilter = (IBaseFilter)new VideoRenderer();
    }

    if(this.videoRendererFilter != null)
    {
        this.graphBuilder.AddFilter(this.videoRendererFilter, 
            "Video Renderer");
    }
    return false;
}

The video renderer is put in the graph via RenderStream:

C#
#if DSHOWNET
hr = captureGraphBuilder.RenderStream(ref cat, ref med, videoDeviceFilter, 
    null, this.videoRendererFilter);
#else
hr = captureGraphBuilder.RenderStream(DsGuid.FromGuid(cat), 
    DsGuid.FromGuid(med), videoDeviceFilter, null, this.videoRendererFilter);
#endif

Using FM Radio

I added this feature because it might be useful when testing code for TV tuners that support FM Radio. FM Radio can be selected only if the TV tuner supports this. I did not add a broadcast station selection list (yet). For testing reasons, it was sufficient for me to switch between the TV-specific code and the FM Radio-specific code.

Switching between TV and FM Radio looks straightforward, but it is not. It might be possible that when the tuner property page is accessed, the new settings should be taken into account. FM Radio does not need video preview, and how do we deal with that? Currently, the video preview is ignored. FM Radio needs different presets; currently, this functionality is not supported.

C#
#if DSHOWNET
private DShowNET.AMTunerModeType TunerModeType
#else
private AMTunerModeType TunerModeType
#endif
{
   get { return this.tunerModeType; }
   set
   {
       this.tunerModeType = value;
       if((this.capture != null)&&(this.capture.Tuner != null))
       {
           this.capture.Tuner.AudioMode = value;
           this.capture.Tuner.InputType = this.tunerInputType;
           this.capture.Tuner.TuningSpace = this.DefaultTuningSpace;
           this.capture.Tuner.CountryCode = this.DefaultCountryCode;
       }
            
       this.numericUpDown1.Enabled = false;
       switch(value)
       {
       case  AMTunerModeType.TV:
           if((this.capture != null)&&(this.capture.Tuner != null))
           {
               this.numericUpDown1.Maximum = this.capture.Tuner.MaxFrequency;
               this.numericUpDown1.Minimum = this.capture.Tuner.MinFrequency;
               this.numericUpDown1.Value = this.LastTvFrequency;
               this.numericUpDown1.Increment = 500000;
               if(this.LastTvFrequency == 0)
               {
                   this.LastTvFrequency = this.tvSelections.GetChannelFrequency;
               }
               this.capture.Tuner.SetFrequency(this.LastTvFrequency);
               this.numericUpDown1.Enabled = true;
           }
           break;
       case AMTunerModeType.FMRadio:
            if((this.capture != null)&&(this.capture.Tuner != null))
            {
                this.capture.Tuner.Channel = this.LastFMRadioFrequency;
                this.numericUpDown1.Minimum = this.capture.Tuner.ChanelMinMax[0];
                this.numericUpDown1.Maximum = this.capture.Tuner.ChanelMinMax[1];
                this.numericUpDown1.Increment = 50000;
                this.numericUpDown1.Value = this.LastFMRadioFrequency;
                this.numericUpDown1.Enabled = true;
            }
            break;
       default:
            break;
       }
   }
}

Features are Made Optional

In the real code example, I added the new features as options. To use a new feature, the corresponding option needs to be selected first. The main reason for doing this is that a program sometimes fails at first use, due to one of the option settings. Then, you can just change the option value and try again. There is one demand: a new value of an option becomes active upon (re)selecting the Audio or Video device. To get the options properly initialized, the function InitMenu() is added. This function should be called when a capture device is (re)selected.

C#
private void initMenu()
{
    if (this.capture != null)
    {
        this.capture.VideoSource = this.capture.VideoSource;
        this.capture.UseVMR9 = this.menuUseVMR9.Checked;
        this.menuUseDeInterlace1.Checked = 
          this.FindDeinterlaceFilter(this.menuUseDeInterlace1.Checked);
    }
}

Points of Interest

Compared with the previous articles, Audio File Saving for the DirectX.Capture Class and Video File Saving in Windows Media Video Format for the DirectX.Capture Class Library, most of the features are kept in, and a number of features have been removed to make it a more usable TV program:

  • Added a TV tuning frequency up/down box to test the new TV fine-tuning functionality. The TV fine-tune functionality is put in a new class TVFineTune which inherits from the original Tuner class. To use the TV tuner's new (and old) functionality in the code, the TVFineTune class should be used instead of the Tuner class.
  • A new class called TVSelections was added, so the TV channel selection functionality becomes far more usable than the one in the original implementation. The implementation is very simple, and it can be modified easily. The channel selections are hard-coded! To use it on your own system, the settings needs to be modified. The code shows three tables: one with the channel names, one with the tuning frequency, and one with the channel number. The use of channel numbers is very system-specific; I added the values to show that it cannot be used that easily unless you know which frequency corresponds with it. Of course, it is possible to write a nice program using Channel to set the TV Tuner and GetVideoFrequnecy() to get the corresponding tuning frequency. Why do this, though, if you already know the TV tuning frequency?
  • Added functionality to initialize country dependent settings. Based on the country dependent settings, this code tries to retrieve the minimum and maximum TV tuning frequency.
  • Change of color space and video standard is supported.
  • Support for TV card drivers that support a video device only. There is no separate audio device to choose, so the audio device needs to be found a little bit differently. The modifications were needed so my Hauppauge PVR150 MCE TV card could be used with the newest TV card driver in the code example.
  • This code example has a possible solution to get TV sound. This solution was needed because there were problems with getting audible sound and the selection of audio sources. The problems also show up in the original version of the DirectX.Capture Class Library written by Brian Low. So, it is not a new problem introduced by my code enhancements. Via debugging, I noticed that in some cases, the audio source and/or video source becomes invalid. As a result of this, exceptions are fired. For that reason, the data causing the problem is reinitialized upon using it in PropertyPages, VideoSources, and/or AudioSources.
  • The code example has been tested with Visual Studio 2003 as well as Visual Studio 2005. Conflicts between these two compiler versions might occur. I added the conditional VS2003 to show the code differences. A difference was that in Visual Studio 2003, a signal was named Closed, while in Visual Studio 2005, this signal has the name FormClosed. This code example has two versions: the Visual Studio 2003 version using DShowNET as the DirectShow interface library and the Visual Studio 2005 version using DirectShowLib-2005 as the DirectShow interface library. It should still be possible to use DShowNET with Visual Studio 2005, but I did not test that. If both Visual Studio versions are needed, then use different directories for this code example to prevent build problems. It is not my intention to solve coding conflicts and build problems that might occur between the several Visual Studio versions.
  • Important: Choose DirectShowLib or DShowNET! The DirectX.Capture class example uses DShowNET. The DirectX.Capture Class Library (Refresh) uses DirectShowLib, which is more complete than DShowNET. It is up to you what to use.
  • The DirectX.Capture examples that go with this article contain new solutions to increase stability. Still, exceptions may occur, but most of them can be solved by either redesigning this code example, or by catching and handling the exceptions in a more appropriate way. Keep in mind that this code example is for learning purposes only. It teaches you how to use DirectShow in C#, and teaches you to use GUI. Exceptions that occur should not be seen as a problem, but as a challenge! The major advantage of an exception is that it tells you when something goes wrong. As a side effect, the program fails and, by debugging, the cause of the problem can be found much easier because you know where to start.

History

  • January 31, 2007: First release.
  • August 1, 2007: Added support for FM Radio and video de-interlacing. The solution of capturing audio via the video device filter has been improved. Furthermore, the code example supports either DShowNET or DirectShowLib as the interface library via the conditional DSHOWNET.
  • August 10, 2007: Added SampleGrabber and VMR9 support in an extra code example.
  • November 28, 2007: Fixed minor bugs in downloads.
  • February 17, 2008: Minor text modifications, links corrected, and the SampleGrabber example has been modified to support Visual Studio 2003 and Visual Studio 2005. Please use either one of these versions only. When both Visual Studio versions are used, put the code in different directories!
  • April 1, 2009: SampleGrabber code example moved to the new article. Minor text modifications, and fixed minor bugs in the downloads.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)