How to Read Barcodes from a Webcam in C# Using DirectShow.NET

This tutorial walks through building a real-time webcam barcode reader for Windows desktop using C#, the DirectShow.NET library, and the Dynamsoft Barcode Reader SDK. DirectShow.NET captures video frames from a USB webcam, while the Dynamsoft Barcode Reader SDK decodes 1D and 2D barcodes from each frame. This approach is ideal for legacy WinForms or WPF desktop applications that need barcode scanning without migrating to newer camera APIs.

DirectShow.NET Webcam barcode reader

What you’ll build: A C# WinForms application that captures live webcam video via DirectShow.NET and decodes 1D/2D barcodes in real time using the Dynamsoft Barcode Reader SDK.

Key Takeaways

  • DirectShow.NET provides low-level webcam access on Windows, making it suitable for legacy desktop barcode scanning applications.
  • The Dynamsoft Barcode Reader SDK decodes 1D and 2D barcodes from raw bitmap frames using the CaptureVisionRouter.Capture() method.
  • A SampleGrabber callback in DirectShow.NET delivers each video frame for barcode processing without blocking the render pipeline.
  • This approach works with any USB webcam on Windows and supports QR Code, Code 128, PDF417, Data Matrix, and other symbologies.

Common Developer Questions

  • How do I read barcodes from a webcam in C# using DirectShow?
  • How to decode QR codes in real time from a webcam stream in a WinForms application?
  • What is the best .NET library for barcode scanning with a USB camera on Windows?

Prerequisites

Step 1: Capture Webcam Video with DirectShow.NET

The DirectShow.NET sample Samples\Capture\PlayCap demonstrates how to open a webcam and display the video stream in C#. Follow these steps:

  1. Get connected devices.

     DsDevice[] devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
    
  2. Create a filter graph and a capture graph.

     int hr = 0;
     this.graphBuilder = (IFilterGraph2)new FilterGraph();
     this.captureGraphBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
     this.mediaControl = (IMediaControl)this.graphBuilder;
     this.videoWindow = (IVideoWindow)this.graphBuilder;
     DsError.ThrowExceptionForHR(hr);
    
  3. Set the filter graph to the capture graph.

     hr = this.captureGraphBuilder.SetFiltergraph(this.graphBuilder);
     DsError.ThrowExceptionForHR(hr);
    
  4. Bind the moniker to a filter object.

     int hr = 0;
     IEnumMoniker classEnum = null;
     IMoniker[] moniker = new IMoniker[1];
     object source = null;
        
     ICreateDevEnum devEnum = (ICreateDevEnum)new CreateDevEnum();
        
     hr = devEnum.CreateClassEnumerator(FilterCategory.VideoInputDevice, out classEnum, 0);
     DsError.ThrowExceptionForHR(hr);
        
     Marshal.ReleaseComObject(devEnum);
        
     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.");
     }
        
     if (classEnum.Next(moniker.Length, moniker, IntPtr.Zero) == 0)
     {
         Guid iid = typeof(IBaseFilter).GUID;
         moniker[0].BindToObject(null, null, ref iid, out source);
     }
     else
     {
         throw new ApplicationException("Unable to access video capture device!");
     }
        
     Marshal.ReleaseComObject(moniker[0]);
     Marshal.ReleaseComObject(classEnum);
        
     return (IBaseFilter)source;
    
  5. Add a camera source to the graph.

     hr = this.graphBuilder.AddFilter(sourceFilter, "Video Capture");
     DsError.ThrowExceptionForHR(hr);
    
  6. Configure a sample grabber and set a callback function for the video stream.

     sampleGrabber = new SampleGrabber() as ISampleGrabber;
        
     {
                 AMMediaType media;
                 int hr;
                 media = new AMMediaType();
                 media.majorType = MediaType.Video;
                 media.subType = MediaSubType.RGB24;
                 media.formatType = FormatType.VideoInfo;
                 hr = sampleGrabber.SetMediaType(media);
                 DsError.ThrowExceptionForHR(hr);
                 DsUtils.FreeAMMediaType(media);
                 media = null;
                 hr = sampleGrabber.SetCallback(this, 1);
                 DsError.ThrowExceptionForHR(hr);
     }
    
     hr = this.graphBuilder.AddFilter(sampleGrabber as IBaseFilter, "Frame Callback");
     DsError.ThrowExceptionForHR(hr);
    
  7. Configure the video stream 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);
         DsError.ThrowExceptionForHR(hr);
        
         // 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);
         DsError.ThrowExceptionForHR(hr);
        
         DsUtils.FreeAMMediaType(mediaType);
         mediaType = null;
     }
    
  8. Render the video stream.

     hr = this.captureGraphBuilder.RenderStream(PinCategory.Preview, MediaType.Video, sourceFilter, (sampleGrabber as IBaseFilter), null);
     DsError.ThrowExceptionForHR(hr);
    
  9. Output the video stream to a PictureBox and resize the video window position.

     int hr = 0;
     hr = this.videoWindow.put_Owner(pictureBox1.Handle);
     DsError.ThrowExceptionForHR(hr);
        
     hr = this.videoWindow.put_WindowStyle(WindowStyle.Child);
     DsError.ThrowExceptionForHR(hr);
        
     hr = this.videoWindow.put_Visible(OABool.True);
     DsError.ThrowExceptionForHR(hr);
        
     Rectangle rc = pictureBox1.ClientRectangle;
     hr = videoWindow.SetWindowPosition(0, 0, _previewWidth, _previewHeight);
     DsError.ThrowExceptionForHR(hr);
    
  10. Start the video stream.

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

Step 2: Decode 1D/2D Barcodes with Dynamsoft Barcode Reader SDK

The following steps demonstrate how to use the Dynamsoft Barcode Reader SDK for barcode detection:

  1. Set the license key and instantiate a CaptureVisionRouter object in Form1():

     using Dynamsoft.DBR;
     using Dynamsoft.Core;
     using Dynamsoft.CVR;
     using Dynamsoft.License;
    
     private CaptureVisionRouter cvr;
    
     public Form1()
     {
         InitializeComponent();
        
         string errorMsg;
         int errorCode = LicenseManager.InitLicense("LICENSE-KEY", out errorMsg);
         if (errorCode != (int)EnumErrorCode.EC_OK)
             Console.WriteLine("License initialization error: " + errorMsg);
            
         cvr = new CaptureVisionRouter();
     }
    
  2. Retrieve the bitmap data and convert it to ImageData for barcode detection:

     private void ReadBarcode(Bitmap bitmap)
     {
         Bitmap nonIndexedBitmap = EnsureNonIndexedFormat(bitmap);
        
         // Convert Bitmap to ImageData
         byte[] bytes;  
         int width;
         int height;
         int stride;
         PixelFormat pixelFormat; 
         GetBitmapData(nonIndexedBitmap, out bytes, out width, out height, out stride, out pixelFormat);
        
         EnumImagePixelFormat format = EnumImagePixelFormat.IPF_RGB_888;
         switch (pixelFormat)
         {
             case PixelFormat.Format24bppRgb:
                 format = EnumImagePixelFormat.IPF_RGB_888;
                 break;
             case PixelFormat.Format32bppArgb:
                 format = EnumImagePixelFormat.IPF_ARGB_8888;
                 break;
             default:
                 MessageBox.Show("Unsupported pixel format.");
                 return;
         }
        
         ImageData data = new ImageData(bytes, width, height, stride, format);
         CapturedResult result = cvr.Capture(data, PresetTemplate.PT_READ_BARCODES);
        
         textBox1.Clear();
         if (result == null)
         {
             MessageBox.Show("No barcode detected.");
         }
         else if (result.GetErrorCode() != 0)
         {
             MessageBox.Show("Error: " + result.GetErrorCode() + ", " + result.GetErrorString());
         }
         else
         {
             DecodedBarcodesResult barcodesResult = result.GetDecodedBarcodesResult();
             if (barcodesResult != null)
             {
                 BarcodeResultItem[] items = barcodesResult.GetItems();
                 foreach (BarcodeResultItem barcodeItem in items)
                 {
                     Console.WriteLine("Result " + (Array.IndexOf(items, barcodeItem) + 1));
                     Console.WriteLine("Barcode Format: " + barcodeItem.GetFormatString());
                     Console.WriteLine("Barcode Text: " + barcodeItem.GetText());
                 }
        
                 using (Graphics g = Graphics.FromImage(nonIndexedBitmap))
                 {
                     foreach (BarcodeResultItem barcodeItem in items)
                     {
                         string output = "Text: " + barcodeItem.GetFormatString() + Environment.NewLine + "Format: " + barcodeItem.GetFormatString() + Environment.NewLine;
                         Dynamsoft.Core.Point[] points = barcodeItem.GetLocation().points;
        
                         // Draw lines based on four points
                         for (int i = 0; i < points.Length; i++)
                         {
                             Dynamsoft.Core.Point p1 = points[i];
                             Dynamsoft.Core.Point p2 = points[(i + 1) % points.Length];
                             g.DrawLine(new Pen(Color.Red, 3), p1[0], p1[1], p2[0], p2[1]);
                         }
        
                         textBox1.AppendText(output);
                     }
                 }
        
                 if (!isCameraMode)
                 {
                     pictureBox1.Image = nonIndexedBitmap;
                 }
             }
             else
             {
                 textBox1.AppendText("No barcode detected!" + Environment.NewLine);
             }
         }
     }
    

Common Issues and Edge Cases

  • No video capture device detected: If CreateClassEnumerator returns a null enumerator, ensure your USB webcam is connected and its driver is installed. Virtual cameras (e.g., OBS Virtual Camera) may not expose a DirectShow source filter.
  • Indexed pixel format errors: Some webcams return frames in indexed pixel formats (e.g., Format8bppIndexed). The EnsureNonIndexedFormat() helper converts these to Format24bppRgb before passing them to the barcode SDK.
  • Low barcode detection rate on fast-moving objects: If barcodes are blurry due to camera motion, reduce the resolution or frame rate via SetConfigParams to give the sensor more exposure time per frame.

Source Code

https://github.com/yushulx/DirectShow.NET-Webcam-Barcode-Reader