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.

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
SampleGrabbercallback 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?
This article is Part 1 in a 2-Part Series.
Prerequisites
- Visual Studio 2022
- DirectShow.NET library
- Dynamsoft Barcode Reader SDK
- Get a 30-day free trial license for Dynamsoft Barcode Reader
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:
-
Get connected devices.
DsDevice[] devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice); -
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); -
Set the filter graph to the capture graph.
hr = this.captureGraphBuilder.SetFiltergraph(this.graphBuilder); DsError.ThrowExceptionForHR(hr); -
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; -
Add a camera source to the graph.
hr = this.graphBuilder.AddFilter(sourceFilter, "Video Capture"); DsError.ThrowExceptionForHR(hr); -
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); -
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; } -
Render the video stream.
hr = this.captureGraphBuilder.RenderStream(PinCategory.Preview, MediaType.Video, sourceFilter, (sampleGrabber as IBaseFilter), null); DsError.ThrowExceptionForHR(hr); -
Output the video stream to a
PictureBoxand 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); -
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:
-
Set the license key and instantiate a
CaptureVisionRouterobject inForm1():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(); } -
Retrieve the bitmap data and convert it to
ImageDatafor 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
CreateClassEnumeratorreturns 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). TheEnsureNonIndexedFormat()helper converts these toFormat24bppRgbbefore 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
SetConfigParamsto give the sensor more exposure time per frame.
Source Code
https://github.com/yushulx/DirectShow.NET-Webcam-Barcode-Reader