Read Barcode from Webcam Viewer with DirectShow.NET

Previously I shared how to use Touchless, which wraps the win32 APIs of DirectShow, to control webcam in C#. In this article, let’s take a further step to see how to use the more complicated DirectShow.NET APIs to capture the video stream and read barcode from preview frames on Windows.

DirectShow.NET Webcam barcode reader

Resources for Learning DirectShow.NET

Webcam Viewer with DirectShow.NET

If you have downloaded the samples of DirectShow.NET, you can open Samples\Capture\PlayCap to learn how to implement a basic webcam viewer.

Get connected devices

DsDevice[] devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);

If there is nothing connected, the returned array size is zero.

Get DirectShow interfaces

int hr = 0;

this.graphBuilder = (IFilterGraph2)new FilterGraph();
this.captureGraphBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
this.mediaControl = (IMediaControl)this.graphBuilder;
this.videoWindow = (IVideoWindow)this.graphBuilder;

Attach the filter graph to the capture graph

hr = this.captureGraphBuilder.SetFiltergraph(this.graphBuilder);

Bind Moniker to a filter object

Use the first video capture device:

            int hr = 0;
            IEnumMoniker classEnum = null;
            IMoniker[] moniker = new IMoniker[1];
            object source = null;

            // Create the system device enumerator
            ICreateDevEnum devEnum = (ICreateDevEnum)new CreateDevEnum();

            // Create an enumerator for the video capture devices
            hr = devEnum.CreateClassEnumerator(FilterCategory.VideoInputDevice, out classEnum, 0);

            // The device enumerator is no more needed

            // If there are no enumerators for the requested type, then 
            // CreateClassEnumerator will succeed, but classEnum will be NULL.
            if (classEnum == null)
                throw new ApplicationException("No video capture device was detected.\r\n\r\n" +
                                               "This sample requires a video capture device, such as a USB WebCam,\r\n" +
                                               "to be installed and working properly.  The sample will now close.");

            // Use the first video capture device on the device list.
            // Note that if the Next() call succeeds but there are no monikers,
            // it will return 1 (S_FALSE) (which is not a failure).  Therefore, we
            // check that the return code is 0 (S_OK).

            if (classEnum.Next(moniker.Length, moniker, IntPtr.Zero) == 0)
                // Bind Moniker to a filter object
                Guid iid = typeof(IBaseFilter).GUID;
                moniker[0].BindToObject(null, null, ref iid, out source);
                throw new ApplicationException("Unable to access video capture device!");

            // Release COM objects

            // An exception is thrown if cast fail
            return (IBaseFilter)source;

Add camera source to graph

hr = this.graphBuilder.AddFilter(sourceFilter, "Video Capture");

Add SampleGrabber to graph

Configure SampleGrabber and set callback function for video stream:

sampleGrabber = new SampleGrabber() as ISampleGrabber;

            AMMediaType media;
            int hr;
            // Set the media type to Video/RBG24
            media = new AMMediaType();
            media.majorType = MediaType.Video;
            media.subType = MediaSubType.RGB24;
            media.formatType = FormatType.VideoInfo;
            hr = sampleGrabber.SetMediaType(media);
            media = null;
            hr = sampleGrabber.SetCallback(this, 1);

Add the filter to graph:

hr = this.graphBuilder.AddFilter(sampleGrabber as IBaseFilter, "Frame Callback");

Configure the preview settings

Set the width, height, and color format:

private void SetConfigParams(ICaptureGraphBuilder2 capGraph, IBaseFilter capFilter, int iFrameRate, int iWidth, int iHeight)
            int hr;
            object config;
            AMMediaType mediaType;
            // Find the stream config interface
            hr = capGraph.FindInterface(
                PinCategory.Capture, MediaType.Video, capFilter, typeof(IAMStreamConfig).GUID, out config);

            IAMStreamConfig videoStreamConfig = config as IAMStreamConfig;
            if (videoStreamConfig == null)
                throw new Exception("Failed to get IAMStreamConfig");

            // Get the existing format block
            hr = videoStreamConfig.GetFormat(out mediaType);

            // copy out the videoinfoheader
            VideoInfoHeader videoInfoHeader = new VideoInfoHeader();
            Marshal.PtrToStructure(mediaType.formatPtr, videoInfoHeader);

            // if overriding the framerate, set the frame rate
            if (iFrameRate > 0)
                videoInfoHeader.AvgTimePerFrame = 10000000 / iFrameRate;

            // if overriding the width, set the width
            if (iWidth > 0)
                videoInfoHeader.BmiHeader.Width = iWidth;

            // if overriding the Height, set the Height
            if (iHeight > 0)
                videoInfoHeader.BmiHeader.Height = iHeight;

            // Copy the media structure back
            Marshal.StructureToPtr(videoInfoHeader, mediaType.formatPtr, false);

            // Set the new format
            hr = videoStreamConfig.SetFormat(mediaType);

            mediaType = null;

Render the preview

hr = this.captureGraphBuilder.RenderStream(PinCategory.Preview, MediaType.Video, sourceFilter, (sampleGrabber as IBaseFilter), null);

Video Window Configuration

Set PictureBox as the video window and resize the video position:

            int hr = 0;
            // Set the video window to be a child of the PictureBox
            hr = this.videoWindow.put_Owner(pictureBox1.Handle);

            hr = this.videoWindow.put_WindowStyle(WindowStyle.Child);

            // Make the video window visible, now that it is properly positioned
            hr = this.videoWindow.put_Visible(OABool.True);

            // Set the video position
            Rectangle rc = pictureBox1.ClientRectangle;
            hr = videoWindow.SetWindowPosition(0, 0, _previewWidth, _previewHeight);

Start webcam preview

rot = new DsROTEntry(this.graphBuilder);
hr = this.mediaControl.Run();

Asynchronously Read Barcode

Because it takes the time to read barcodes, once the callback function triggered, we should call the algorithm method and render GUI asynchronously. Do not forget to flip the bitmap:

public int BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen)

            Bitmap v = new Bitmap(_previewWidth, _previewHeight, _previewStride,
                PixelFormat.Format24bppRgb, pBuffer);

            if (isFinished)
                    isFinished = false;
                    isFinished = true;
            return 0;

Source Code