| Cinema software |
Source code & API

Multicast UDP source filter

Directshow filter for MPEG-TS streams encapsulated in multicast/unicast UDP, RTP or HTTP

This filter can receive IPv4 UDP multicast raw MPEG-TS and RTP encapsulated MPEG-TS streams. It has 3 output pins: video, audio and subtitles. Application can connect subtitle pin directly to video renderer or get for graphical subtitles directly from the filter via IUdpMulticast interface. Application can change audio or subtitle track on the fly. IUdpMulticast interface is used to configure the filter and to get informations about the stream. Inbuilt parser doesn't just parse the MPEG-TS itself, but also elementary streams: MPEG1/2, H264 video and MPEG1/2, AC3, AAC audio streams. These media types are the only ones supported by this filter. Beside basic informations from PAT and PMT tables it can also parse service descriptor from SDT table and delivery system descriptor from NIT table. Filter can also measure bitrate of whole stream and bitrate for packets with the same PID. Playback rates higher than 1x are also supported. Filter can be configured in PLAYBACK or PARSING mode. Playback mode must be used in a player, parsing mode, where only parser is activated, can be used for a channel scanner. Filter can also open and play MPEG-TS files. Manifest file is included with filter, so that application can use registration-free activation of com object and therefore filter doesn't need to be registered with regsrv32.

Download Multicast Udp source filter 0.96.8 (1.6.2018)

Older releases

Download Multicast Udp source filter 0.96.2
Download Multicast Udp source filter 0.96.0
Download Multicast Udp source filter 0.95.2
Download Multicast Udp source filter 0.94.9
Download Multicast Udp source filter 0.94.2
Download Multicast Udp source filter 0.94.1
Download Multicast Udp source filter 0.93.3
Download Multicast Udp source filter 0.93.0

Testing the filter with GraphEdit or GraphStudio


In order to test the filter with GraphEdit, it needs to be registered first:
regsvr32 "Multicast udp source filter.dll"
Add filter to the GraphStudio.
Set URL, which should be in format udp://[multicast address]:port or rtp://[multicast address]:port.
Render the pins and run the graph.
Note that filter stops receiving data only when removed from graph.

Using filter in application

There are 3 ways to add and configure filter to a graph.
  1. In first method, demultiplexer is automatically configured based on preffered language and track type. IFileSourceFilter interface is used to load stream. This method is provided for easier testing from GraphEdit or GraphStudio and quick test.
    Add filter to graph, query filter for IUdpMulticast interface, use functions SetAutoConfPreferredTrack and SetAutoConfPreferredAudioType to configure preffered audio and subtitle track. Query for IFileSourceFilter interface and Load URL in format shown above. Load function will block until parser is working, which might take a couple of seconds. You can then use functions GetAudioPid, GetVideoPid and GetSubtitlesPid to see which tracks were selected. To get more informations about the tracks, use function GetParsedData and for further details use functions: GetVideoInfo, GetAudioInfo, GetSubDesc as shown in example below. In C# wrapper all needed informations are stored in a single list (see examples). After all the pins are successfully connected, run the graph.
  2. Second method is the same as the first, except functions SetMulticastIp, SetNicIp and SetPort from IUdpMulticast interface are used to load multicast address. After address and preferred languages of tracks are set, application must call ParseStream(FALSE). After you receive EC_PARSER_COMPLETE message you may use the same functions as in method a.
  3. In third method you don't need to set preffered languages, but you set the actual PIDs after you receive EC_PARSER_COMPLETE by calling function ConfigureDemultiplexer with video, audio and subtitle PIDs. Before calling ParseStream, set DEMUX_FLAGS_MANUAL_CONFIG flag (SetDemultiplexerFlags). This method is recommended, because application has full control of initial track selection. Before calling ConfigureDemultiplexer, use the same functions described in method a to get informations about the stream.

C++ example

Visual Studio configuration: Include , "mudpf.h","mudpfifc_i.c". Mudpfifc_h is already included by mudpf.h. Link against Strmiids.lib and Ws2_32.lib. In Manifest tool under AdditionalManifestFiles add Multicast udp source filter.sxs.manifest. Recommended settings: use unicode charset and MT(d) runtime library.

#include <initguid.h> 
#include <dshow.h>
#include "mudpf.h"
#include "mudpfifc_i.c"

void main(void)
{
	CHAR test_maddress[] = "232.4.1.1";
	u_short test_port = 5002;

	IGraphBuilder *pGraph = NULL;
	IMediaControl *pControl = NULL;
	IMediaEvent   *pEvent = NULL;
	IBaseFilter   *pMudFilter = NULL;
	IUdpMulticast *pMudpConf = NULL;
	ICaptureGraphBuilder2 * pCaptureGraphBuilder = NULL;
	IBaseFilter* pVmr9Filter = NULL; //VMR9 video renderer

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

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

	hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
	hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);

	// Create VMR9 filter
	hr = CoCreateInstance(CLSID_VideoMixingRenderer9, NULL,	CLSCTX_INPROC, IID_IBaseFilter, reinterpret_cast<void**>(&pVmr9Filter));
	if (FAILED(hr))
	{
		printf("ERROR - Could not create VMR-9 Video Renderer.");
		goto exit;
	}

	// Create Udp source filter
	hr = CoCreateInstance(CLSID_UdpSourceFilter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**> (&pMudFilter));
	if (FAILED(hr))
	{
		printf("ERROR - Could not create Udp Source Filter.");
		goto exit; // Goto is used here to simplify this example.
	}

	hr = pMudFilter->QueryInterface(IID_IUdpMulticast, reinterpret_cast<void**>(&pMudpConf));

	// Configure Udp Source Filter as described in method b.
	pMudpConf->SetMulticastIp(inet_addr(test_maddress));
	pMudpConf->SetPort(test_port);
	// Use any NIC. For multiple NICs, ipv4 address or index (SetNicIndex) of appropriate one must be set.
	pMudpConf->SetNicIp(0); 
	// Tell demultiplexer to initially set english audio track if possible.
	pMudpConf->SetAutoConfPreferredTrack(LANGTRACK_AUDIO, "eng"); 
	// Tell demultiplexer to initially set english subtitle track if possible.
	pMudpConf->SetAutoConfPreferredTrack(LANGTRACK_SUBTITLE, "eng");
	// Tell demultiplexer to initially set MPEG2 audio track if possible.
	pMudpConf->SetAutoConfPreferredAudioType(AUD_TRCK_MPEG2); 

	// Add Udp Source Filter to the graph.
	hr = pGraph->AddFilter(pMudFilter, L"UDP MPEG-TS Multicast Receiver");

	// Add VMR-9 Video renderer to the graph.
	hr = pGraph->AddFilter(pVmr9Filter, L"VMR-9");

	// Start the parser of Udp Source Filter.
	// In this example we wait until stream is parsed. This can block up to 6 seconds.
	// In a real application set wait parameter to FALSE and later handle EC_PARSER_COMPLETE event (in WndProc) ! 
	hr = pMudpConf->ParseStream(TRUE); // Real application can't block here, also check for ERR_RECV_IPAGN_AND_IPV4_BIND_FAILED error.

	// The Capture Graph Builder 2 is a helper object for building graphs.
	hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, reinterpret_cast<void**>(&pCaptureGraphBuilder));
	if (SUCCEEDED(hr))
		hr = pCaptureGraphBuilder->SetFiltergraph(pGraph);
	else
		goto exit;

	//Render video pin
	hr = pCaptureGraphBuilder->RenderStream(NULL, &MEDIATYPE_Video, pMudFilter, NULL, pVmr9Filter);
	if (FAILED(hr))
	{
		printf("ERROR - Could not render video pin.");
		goto exit;
	}
	//Render audio pin
	hr = pCaptureGraphBuilder->RenderStream(NULL, &MEDIATYPE_Audio, pMudFilter, NULL, NULL);
	if (FAILED(hr))
	{
		printf("ERROR - Could not render audio pin.");
		goto exit;
	}  

	//printBasicStreamInfo(pMudpConf);
	
	pControl->Run();

	if (FAILED(hr))
		goto exit;

	MessageBox(NULL, L"Click me to end playback.", L"DirectShow", MB_OK);

    exit:
	if (pCaptureGraphBuilder != NULL)
		pCaptureGraphBuilder->Release();
	if (pMudpConf != NULL)
		pMudpConf->Release();
	if (pMudFilter != NULL)
		pMudFilter->Release();
	if (pVmr9Filter != NULL)
		pVmr9Filter->Release();
	pControl->Release();
	pEvent->Release();
	pGraph->Release();
	CoUninitialize();
}

Print some basic informations about the tracks:

void printBasicStreamInfo(IUdpMulticast *pMudpConf)
{
	interop_piddetails_t p_det;
	pesaudiodet_t aud_det;
	pesvideodet_t vid_det;
	subtitling_t sub_det;
	int progs;
	pMudpConf->GetProgCount(&progs);

	for (int prog = 0; prog < progs; prog++)
	{
		printf("Program %d \n", prog);
		int prog_pids = 0;
		pMudpConf->GetPidCount(prog, &prog_pids);
		for (int p = 0; p < prog_pids; p++)
		{
			pMudpConf->GetParsedData(prog, p, &p_det);
			printf(" PID= %d type=%d lang= %s PCR=%d ", p_det.pid, p_det.stream_type, p_det.language, p_det.pcr_pid);
			switch (p_det.stream_type)
			{
			case AUD_TRCK_MPEG1:
			case AUD_TRCK_MPEG2:
			case AUD_TRCK_DOLBY:
			case AUD_TRCK_AAC:
				pMudpConf->GetAudioInfo(prog, p, &aud_det);
				printf("Audio: bitrate=%d channels=%d freq=%dHz", aud_det.bitrate, aud_det.channels, aud_det.sampfreq);
				break;

			case VID_TRCK_MPEG1:
			case VID_TRCK_MPEG2:
			case VID_TRCK_H264:
				pMudpConf->GetVideoInfo(prog, p, &vid_det);
				printf("Video: dim=%dx%d AR/PAR x=%d y=%d bitrate=%d profile=%d level=%d fps=%d/%d afd=%d int=%d",
					vid_det.width, vid_det.height, vid_det.aspect_x, vid_det.aspect_y, vid_det.bitrate, 
					vid_det.profile, vid_det.level, vid_det.timeperfieldNom, vid_det.timeperfieldDenom, vid_det.afd, 
					vid_det.interlaced_indicator);

			// 0x06 = Private PES	
			case 0x06: 
				if (p_det.subtitling_descriptor_count > 0 && p_det.teletext_descriptor_count == 0)
				{
				    // Get first subtitling descriptor
					pMudpConf->GetSubDesc(prog, p, 0, &sub_det); 
					printf("Subtitles: %s", sub_det.language);
				}
				break;
			default:
				break;

			}
			printf("\n");
		}
		printf("\n");
	}
	u_short apid, vpid, spid;
	pMudpConf->GetVideoPid(&vpid);
	pMudpConf->GetAudioPid(&apid);
	pMudpConf->GetSubtitlesPid(&spid);
	printf("Selected PIDS: %d %d %d",vpid,apid,spid);
}

Put Multicast udp source filter.sxs.manifest and Multicast udp source filter.dll in the same folder as exe.

C# example

Create new Windows Forms Application, right click on References, add UdpSourceTSParser.dll and Multicast udp source filter.sxs.manifest.
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using UdpSourceTSParser;
using UdpSourceTSParser.DirectShowLib;
using UdpSourceTSParser.MediaFoundation;
using UdpSourceTSParser.MediaFoundation.EVR;
using UdpSourceTSParser.MediaFoundation.Misc;
using System.Runtime.InteropServices;

//# Example of a multicast udp MPEG_TS player with EVR renderer without error handling.

namespace DirectshowCSharpSample
{
    public partial class Form1 : Form
    {
        public const int WM_GRAPHNOTIFY = 0x8000 + 1;
        IPAddress m_mcast_address = IPAddress.Parse("232.4.1.1");
        ushort m_port = 5002;
        TSParser m_parser = null;                       // Helper class for nicer presentation of stream informations. 
        IBaseFilter m_UdpSource = null;                 // UDP source and demultiplexer filter .
        IUdpMulticast m_IUdpMulticast = null;           // Interface exposed by UDP source and demultiplexer filter,
                                                        // used to configure filter.
        IFilterGraph2 m_FilterGraph = null;
        IMediaControl m_mediaCtrl = null;
        IMediaEventEx m_mediaEvent = null;
        IBaseFilter m_evr = null;                       // EVR renderer, used on Vista and later.
        IMFVideoDisplayControl m_evrdisplayCtrl = null; // EVR display control interface.

        public Form1()
        {
            InitializeComponent();
            this.Load += Form1_Load;
            this.Resize += Form1_Resize;
            this.FormClosing += Form1_FormClosing;
        }

        void Form1_Load(object sender, EventArgs e)
        {
            int hr = 0;
            m_FilterGraph = (IFilterGraph2)new FilterGraph();
			// Create Udp source and TS demultiplexer filter.
            m_UdpSource = (IBaseFilter)new UdpSourceFilter();   
            // Query UdpMulticast source filter for IUdpMulticast interface.
			m_IUdpMulticast = (IUdpMulticast)m_UdpSource;       
            // Configure Udp Source Filter.
            hr = m_IUdpMulticast.SetMulticastIp(BitConverter.ToUInt32(m_mcast_address.GetAddressBytes(), 0));
            hr = m_IUdpMulticast.SetPort(m_port);
			// Use ip address of appropriate NIC in a real application.
            hr = m_IUdpMulticast.SetNicIp(BitConverter.ToUInt32(IPAddress.Any.GetAddressBytes(), 0)); 
            hr = m_IUdpMulticast.SetAutoConfPreferredTrack((ushort)UdpSourceTSParser.lang_track_enum_t.LANGTRACK_AUDIO, "eng");
            hr = m_IUdpMulticast.SetAutoConfPreferredTrack((ushort)UdpSourceTSParser.lang_track_enum_t.LANGTRACK_SUBTITLE, "eng");
            hr = m_IUdpMulticast.SetAutoConfPreferredAudioType((ushort)UdpSourceTSParser.audio_track_enum_t.AUD_TRCK_MPEG2);
            //* In real application you will probably want to configure demultiplexer manually and not base on preffered types and languages.
            // m_IUdpMulticast.SetDemultiplexerFlags(UdpSourceTSParser.TSParser.DEMUX_FLAGS_MANUAL_CONFIG);
            // Create helper object .
			m_parser = new TSParser(m_IUdpMulticast); 
            // Add the the filter.
            hr = m_FilterGraph.AddFilter(m_UdpSource, "Udp Source and TS Parser Filter (MulticastTV)");
            m_mediaEvent = (IMediaEventEx)m_FilterGraph;
            m_mediaEvent.SetNotifyWindow(this.Handle, WM_GRAPHNOTIFY, IntPtr.Zero);
            m_mediaCtrl = (IMediaControl)m_FilterGraph;
            hr = m_IUdpMulticast.ParseStream(false);  // Check for ERR_RECV_IPAGN_AND_IPV4_BIND_FAILED error.
            // Configure and add EVR video renderer. This won't work on Win xp.
            m_evr = (IBaseFilter)new EnhancedVideoRenderer();
            configureEVR();
            hr = m_FilterGraph.AddFilter(m_evr, "EVR");
        }

        void Form1_Resize(object sender, EventArgs e)
        {
            if (m_evrdisplayCtrl != null)
            {
                MFVideoNormalizedRect src = new MFVideoNormalizedRect(0, 0, 1, 1);
                MFRect clientrect = UdpSourceTSParser.MediaFoundation.Misc.MFRect.FromRectangle(this.ClientRectangle);
                m_evrdisplayCtrl.SetVideoPosition(src, clientrect);
            }
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_GRAPHNOTIFY)
                handleGraphEvents(); 

            base.WndProc(ref m);
        }

        void handleGraphEvents()
        { 
            int hr;
            IntPtr p1, p2;
            EventCode ec;

            while ((hr = m_mediaEvent.GetEvent(out ec, out p1, out p2, 0)) >= 0)
            {
                hr = m_mediaEvent.FreeEventParams(ec, p1, p2);
                if ((int)ec == TSParser.EC_PARSER_COMPLETE)
                {   // Because DEMUX_FLAGS_MANUAL_CONFIG was not set here, 
                    // all you need to do is to construct video, audio and subtitle chain.
                    constructChain();
                }
            }
        }

        // Must be called when parser completes.
        void constructChain()
        {
            int hr;
			// Load parsing results from the filter into the list (parser.ProgrameList) !
            m_parser.GetInfo(); 
            if (m_parser.ProgrameList.Count == 0)
                return;

            // Print some info in the title bar.
            if (m_parser.ProgrameList[0].Video.Count > 0)
                this.Text += " Vid: PID=" + m_parser.ProgrameList[0].Video[0].PID + " " +
                    m_parser.ProgrameList[0].Video[0].Width.ToString() + "X" +
                    m_parser.ProgrameList[0].Video[0].Height.ToString();
            if (m_parser.ProgrameList[0].Audio.Count > 0)
                this.Text += " Aud: Count=" + m_parser.ProgrameList[0].Audio.Count + " PID= " +
                    m_parser.selectedAudioPID;

            //* In real application you will examine ProgrameList and configure demultiplexer here:
            //m_IUdpMulticast.ConfigureDemultiplexer(m_parser.ProgrameList[0].Video[0].PID, m_parser.ProgrameList[0].Audio[0].PID, 0);

            // ICaptureGraphBuild2 interface to construct filter chain.
            ICaptureGraphBuilder2 gbuild2 = null;
            gbuild2 = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
            hr = gbuild2.SetFiltergraph(m_FilterGraph);
            if (m_parser.ProgrameList[0].Video.Count > 0)
                hr = gbuild2.RenderStream(null, MediaType.Video, m_UdpSource, null, m_evr); // Video chain
            if (m_parser.ProgrameList[0].Audio.Count > 0)
                hr = gbuild2.RenderStream(null, MediaType.Audio, m_UdpSource, null, null);  // Audio chain
           
		   // Run the graph
            hr = m_mediaCtrl.Run();  
            Marshal.ReleaseComObject(gbuild2);
        }

        void configureEVR()
        {
            object o;
            IMFGetService pGetService = null;
            pGetService = (IMFGetService)m_evr;
            pGetService.GetService(MFServices.MR_VIDEO_RENDER_SERVICE, typeof(IMFVideoDisplayControl).GUID, out o);
            try
            {
                m_evrdisplayCtrl = (IMFVideoDisplayControl)o;
            }
            catch
            {
                Marshal.ReleaseComObject(o);
                m_evrdisplayCtrl = null;
                throw;
            }
            m_evrdisplayCtrl.SetVideoWindow(this.Handle);                               // Parent window
            m_evrdisplayCtrl.SetAspectRatioMode(MFVideoAspectRatioMode.PreservePicture);// Aspect ratio
            Rectangle r = this.ClientRectangle;                                         // Position
            MFRect rc = new MFRect(r.Left, r.Top, r.Right, r.Bottom);
            m_evrdisplayCtrl.SetVideoPosition(null, rc);        
        }

        void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (m_IUdpMulticast != null)
                Marshal.ReleaseComObject(m_IUdpMulticast);
            if (m_evrdisplayCtrl != null)
            { 
                Marshal.ReleaseComObject(m_evrdisplayCtrl);
                m_evrdisplayCtrl = null;
            }
            if (m_evr != null)
                Marshal.ReleaseComObject(m_evr);
            if (m_mediaEvent != null)
                Marshal.ReleaseComObject(m_mediaEvent);
            if (m_mediaCtrl != null)
                Marshal.ReleaseComObject(m_mediaCtrl);
            if (m_FilterGraph != null)
                Marshal.ReleaseComObject(m_FilterGraph);              
        }
    }  
}

Non-standard functions

Audio or subtitle track is normally selected via IAMStreamSelect, however this interface is currently not implemented in this filter.
Subtitle track can be easily changed by calling SetSubtitlesPid.
To change audio track, a little more work is needed, especially if you need to reconnect source filter to a different audio decoder.

Example of audio track change on the fly in a c++ application.

HRESULT changeAudio(u_short pid)
{
	//If format is exactly the same, we can only change PID in demultiplexer.
	IPin* demuxAudio_OutPin;
	hr = pMudFilter->FindPin(L"Audio", &demuxAudio_OutPin);
	FAIL_RET(hr);
	
	// Block flow from pins.
	IPinFlowControl* fc=NULL;
	hr = demuxAudio_OutPin->QueryInterface(IID_IPinFlowControl, reinterpret_cast<void**>(&fc));
	if (hr != S_OK)
	{
		demuxAudio_OutPin->Release();
		return hr;
	}
	fc->Block(AM_PIN_FLOW_CONTROL_BLOCK, NULL);

	IPin* decAudio_InPin=NULL;
	hr = demuxAudio_OutPin->ConnectedTo(&decAudio_InPin);
	if (hr != S_OK)
		goto clean;

	PIN_INFO PinInfo;
	hr = decAudio_InPin->QueryPinInfo(&PinInfo);
	IBaseFilter* decAudio_filter = NULL;
	decAudio_filter = PinInfo.pFilter;

	IGraphConfig* conf=NULL;
	hr = g_pGraph->QueryInterface(IID_IGraphConfig, reinterpret_cast<void**>(&conf));
	if (hr != S_OK)
		goto clean;

	HRESULT aud_act = pMudpConf->SetAudioPid(pid);
	switch (aud_act)
	{
	    case AUDIO_PID_CHANGE_NO_CHANGES_NEEDED:
		    // Just unblock the flow
			fc->Block(0, NULL); 
		break;

		case  AUDIO_PID_CHANGE_FORMAT_CHANGE_NEEDED: 
		case  AUDIO_PID_CHANGE_RECONNECTION_NEEDED:
			hr = g_pGraph->Disconnect(demuxAudio_OutPin);
			hr = conf->Reconnect(demuxAudio_OutPin, decAudio_InPin, NULL, decAudio_filter, NULL, AM_GRAPH_CONFIG_RECONNECT_DIRECTCONNECT);
			fc->Block(0, NULL); 
	   break;
	}

clean:
	if (conf != NULL)
		conf->Release();
	if (decAudio_InPin != NULL)
		decAudio_InPin->Release();
	fc->Release();
	demuxAudio_OutPin->Release();
	return hr;
}

Example of audio track change on the fly in a c# application.

 
        private void changeAudioTrack(ushort track)
        {
            IPin demuxaudioout = null;
            m_UdpSource.FindPin("Audio", out demuxaudioout);
            IPinFlowControl fc = (IPinFlowControl)demuxaudioout;

            // Block flow from pins
            fc.Block(AMPinFlowControl.Block, IntPtr.Zero);
            
            // Change PID and get result;
            TSParser.AudioPidChangeResult changeResult = (TSParser.AudioPidChangeResult)m_IUdpMulticast.SetAudioPid(m_parser.ProgrameList[m_selected_tracks.ProgramIndex].Audio[track].PID); 
            m_selected_tracks.AudioIndex = track;

            switch (changeResult)
            { 
                case TSParser.AudioPidChangeResult.NoChange :
				     // Unblock pin output.
                     fc.Block(AMPinFlowControl.None, IntPtr.Zero); 
                break;

                case TSParser.AudioPidChangeResult.FormatChange:     
                case TSParser.AudioPidChangeResult.ReconnectionNeeded:
                
                    m_FilterGraph.Disconnect(demuxaudioout);

                    IPin ffaudioin = null;
                    m_ffAudio.FindPin("In", out ffaudioin);
 
                    IGraphConfig conf = (IGraphConfig)m_FilterGraph;
                    conf.Reconnect(demuxaudioout, ffaudioin, null, m_ffAudio, IntPtr.Zero, AMGraphConfigReconnect.DirectConnect);
                    // Unblock pin output.
					fc.Block(AMPinFlowControl.None, IntPtr.Zero); 
                    Marshal.ReleaseComObject(ffaudioin);
                break;
            }
            Marshal.ReleaseComObject(fc);
            Marshal.ReleaseComObject(demuxaudioout);
        }

C# example of a channel scanner

Create new Console Application, right click on References, add UdpSourceTSParser.dll and Multicast udp source filter.sxs.manifest.

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Net;
using System.Threading;
using UdpSourceTSParser;
using UdpSourceTSParser.DirectShowLib;

//# Example of a channel scanner without error handling.
namespace ScannerCsharpSample
{
    class Program
    {     
        static IPAddress start_address ;
        static IPAddress stop_address;
        static ushort port;
        static IBaseFilter UdpSource = null;    // UDP source and demultiplexer filter .
        static IUdpMulticast IUdpConf = null;   // Interface exposed by UDP source and demultiplexer filter, used to configure filter.
        static TSParser parser =null;           // Helper class.
        static IFilterGraph2 FilterGraph;
        static ManualResetEvent exitEvent;      // Event for graceful exit on Ctrl-c.
        static ManualResetEvent finishedEvent;

        static void Main(string[] args)
        {
            int hr;
            DateTime ip_scan_start;
            int timeout = 2000; // Wait maximum of 2 seconds for each multicast ip address.
            start_address = IPAddress.Parse("232.4.1.0");
            stop_address = IPAddress.Parse("232.4.4.255");
            port = 5002;
            FilterGraph = (IFilterGraph2)new FilterGraph();
            IMediaEvent mediaEvent = (IMediaEvent)FilterGraph;
            IntPtr hEvent;  // Handle to a manual-reset event that remains signaled while the queue contains event notifications. 
            mediaEvent.GetEventHandle(out hEvent);
            ManualResetEvent mre = new ManualResetEvent(false);
            mre.SafeWaitHandle = new Microsoft.Win32.SafeHandles.SafeWaitHandle(hEvent, true);
            IntPtr p1, p2;
            EventCode ec;
            
            Console.CancelKeyPress += new ConsoleCancelEventHandler(exitHandler);
            Console.WriteLine("Press Ctrl-C to exit.");
            exitEvent = new ManualResetEvent(false);
            finishedEvent = new ManualResetEvent(false);

            IPAddress ip = start_address;
            while ( (ip = incrementIpAddress(ip, stop_address)) != IPAddress.None) 
            {
                startScanner(ip, port);
                Console.Write(ip.ToString() + " : ");
                ip_scan_start = DateTime.Now;

                while (DateTime.Now.Subtract(ip_scan_start).TotalMilliseconds < timeout)
                {
				    // Wait for events, but don't block indefently.
                    mre.WaitOne(100, true); 
                    // Read events:
                    for (hr = mediaEvent.GetEvent(out ec, out p1, out p2, 0);
                          hr >= 0;
                          hr = mediaEvent.GetEvent(out ec, out p1, out p2, 0))
                    {
                        if ((int)ec == TSParser.EC_PARSER_STARTED) 
                        {
                            // Scanner is activated and is waiting for multicast traffic.
                        }

                        else if ((int)ec == TSParser.EC_MSG_ParserRecvdFirstSample) 
                        {
						    // Parser received first sample.
                            Console.Write("Receiving... ");
                        }

                        else if ((int)ec == TSParser.EC_PARSER_COMPLETE) 
                        {
						    // Parser has found all the BASIC data neccessary for creating directshow graph.
							// Load parsing results from the filter into the list (parser.ProgrameList) !
                            parser.GetInfo(); 
                            // Print some info and stop scanning.
                            printSomeInfo();
                            ip_scan_start = DateTime.MinValue; //This will exit the loop after all events are read.
                            // Don't break here if you want to be sure to get other data like service list or NIT table.
                        }

                        else if ((int)ec == TSParser.EC_MSG_SERVICE_LIST_RECEIVED) 
                        {
						   // Parser received service list.
                           List<ServiceDescriptorTriplet> sdesc=  parser.GetServiceDescriptor();
                           if (sdesc.Count > 0)
                               Console.Write("Name: " +sdesc[0].ServiceName+" ");
                        }
                        else
                        {
                            // Don't process other messages.
                            // To see list of all messages, look at TSParser class !
                        }

                        hr = mediaEvent.FreeEventParams(ec, p1, p2);
                    }
                }
                stopScanner();
                Console.WriteLine();
                if (exitEvent.WaitOne(0))
                    break;
            }

            mre.Close();
            Marshal.ReleaseComObject(mediaEvent);
            Marshal.ReleaseComObject(FilterGraph);
            finishedEvent.Set();
        }

        static void startScanner(IPAddress ip, ushort port)
        {
		    int hr;
            UdpSource = (IBaseFilter)new UdpSourceFilter(); 
            IUdpConf = (IUdpMulticast)UdpSource;           
            parser = new TSParser(IUdpConf);     
            IUdpConf.SetMulticastIp(BitConverter.ToUInt32(ip.GetAddressBytes(), 0));
            IUdpConf.SetPort(port);
            IUdpConf.SetNicIp(0);
            IUdpConf.SetMode(TSParser.PARSING_MODE);  // Parsing mode must be set.
            FilterGraph.AddFilter(UdpSource, "Udp Source and TS Parser Filter (MulticastTV)");
            hr = IUdpConf.ParseStream(false); // Check for ERR_RECV_IPAGN_AND_IPV4_BIND_FAILED error.
        }

        static void stopScanner()
        {
             parser.ProgrameList.Clear();
             FilterGraph.RemoveFilter(UdpSource);
             Marshal.ReleaseComObject(IUdpConf);
             Marshal.ReleaseComObject(UdpSource);
        }

        static void printSomeInfo()
        { 
            // Print some audio and video informations from the first program.
            if (parser.ProgrameList.Count == 0)
                return;
            if(parser.ProgrameList[0].Video.Count>0)
                Console.Write("Vid: PID="+parser.ProgrameList[0].Video[0].PID + " typ="+ parser.ProgrameList[0].Video[0].Type  +
                    " dim="+parser.ProgrameList[0].Video[0].Width+"x"+parser.ProgrameList[0].Video[0].Height+" ");
            if (parser.ProgrameList[0].Audio.Count > 0)
                Console.Write("Aud: PID=" + parser.ProgrameList[0].Audio[0].PID + " typ=" + parser.ProgrameList[0].Audio[0].Type +
                    " chn=" + parser.ProgrameList[0].Audio[0].NumberOfChannels + " brt=" + parser.ProgrameList[0].Audio[0].Bitrate +
                    " lng=" + parser.ProgrameList[0].Audio[0].Language +" ");
        }

        static IPAddress incrementIpAddress(IPAddress ip, IPAddress max_ip)
        {
            var bytes = ip.GetAddressBytes();
            Array.Reverse(bytes);
            UInt32 ip_uint = BitConverter.ToUInt32(bytes, 0);
            ip_uint += 1;
            var bytesinc = BitConverter.GetBytes(ip_uint);
            Array.Reverse(bytesinc);
            IPAddress newip =  new IPAddress(bytesinc);
            if (newip.Equals(max_ip))
                return IPAddress.None;
            else
                return newip;
        }

        protected static void exitHandler(object sender, ConsoleCancelEventArgs args)
        {
            Console.WriteLine("Caught Ctrl-C...");
            exitEvent.Set();
            finishedEvent.WaitOne(2500);
        }
    }
}