How to Build .NET Webcam Barcode Scanner with DirectShow

In Dynamsoft Barcode Reader SDK, there is a fancy webcam barcode sample called BarcodeReaderDemo that supports reading barcodes from disk files, TWAIN-compatible hardware scanners, and USB webcams. However, one thing you have to know is the functionality of acquiring images from peripheral devices relies on Dynamic .NET TWAIN, which is also charged. Fortunately, the image viewer library and image codecs library are free to use. In this article, I will share how to remove the scanner module from the project and use DirectShow for webcam control.

What You Should Know about the Dynamsoft Barcode Reader Demo

Download and install Dynamsoft Barcode Reader v7.3.

The BarcodeReaderDemo project is located at **\Samples\Desktop\C#\BarcodeReaderDemo**. It is powerful and can do various barcode scanning tests.

read barcode from scanner

The project-dependent libraries include Dynamsoft.BarcodeReader.dll, Dynamsoft.ImageCore.dll, Dynamsoft.Forms.Viewer.dll, Dynamsoft.Camera.dll, Dynamsoft.PDF.dll, and Dynamsoft.Twain.dll.

The Windows installer will automatically generate trial licenses for both Dynamsoft Barcode Reader and Dynamic .NET TWAIN to App.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key ="DBRLicense" value ="DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="/>
    <add key ="DNTLicense" value ="DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="/>
  </appSettings>
</configuration>

Note: the Dynamsoft Barcode Reader license is for Dynamsoft.BarcodeReader.dll  and Dynamic .NET TWAIN license is for Dynamsoft.Camera.dll, Dynamsoft.PDF.dll and Dynamsoft.Twain.dll.

DirectShowNet for Webcam Control

While looking for C# code for webcam programming, you may notice OpenCV is often recommended by many developers. The camera APIs provided by OpenCV is truly easy to use. However, OpenCV is not suitable for getting and setting complicated camera parameters, such as enumerating multiple cameras and related resolutions. To fully control a webcam in C#, we can use DirectShowNet on Windows 10. To learn how to quickly create a simple webcam viewer with DirectShowNet, you can read the article: Read Barcode from Webcam Viewer with DirectShow.NET.

Webcam Barcode Scanner with DirectShowNet

In the following paragraphs, I will modify the BarcodeReaderDemo project to replace Dynamic .NET TWAIN with DirectShow for webcam programming.

Code Change

Remove the scanner tab in BarcodeReaderDemo.cs:

// remove
mRoundedRectanglePanelAcquireLoad.Controls.Add(mThAcquireImage);

Beautify the remaining tabs by adjusting their sizes:

// before
mThLoadImage.Size = new Size(103, 40);
mThWebCamImage.Location = new Point(207, 1);
mThWebCamImage.Size = new Size(103, 40);
// after
mThLoadImage.Size = new Size(156, 40);
mThWebCamImage.Location = new Point(157, 1);
mThWebCamImage.Size = new Size(156, 40);

dotnet webcam barcode reader

Remove Dynamic .NET TWAIN relevant code:

// remove
mTwainManager = new TwainManager(dntLicenseKeys);
mCameraManager = new CameraManager(dntLicenseKeys);
mPDFRasterizer = new PDFRasterizer(dntLicenseKeys);

Create a DSManager.cs file to implement the DirectShow logic.

Define two structures to store camera information:

        public struct Resolution
        {
            public Resolution(int width, int height)
            {
                Width = width;
                Height = height;
            }

            public int Width { get; }
            public int Height { get; }

            public override string ToString() => $"({Width} x {Height})";
        }

        public struct CameraInfo
        {
            public CameraInfo(DsDevice device, List<Resolution> resolutions)
            {
                Device = device;
                Resolutions = resolutions;
            }

            public DsDevice Device { get; }
            public List<Resolution> Resolutions { get; }
        }

Enumerate camera devices:

            DsDevice[] devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
            if (devices != null)
            {
                cameras = new List<CameraInfo>();
                foreach (DsDevice device in devices)
                {
                    List<Resolution> resolutions = GetAllAvailableResolution(device);
                    cameras.Add(new CameraInfo(device, resolutions));
                }
            }

The following code snippet of fetching camera resolutions is from StackOverflow:

        private List<Resolution> GetAllAvailableResolution(DsDevice vidDev)
        {
            try
            {
                int hr, bitCount = 0;

                IBaseFilter sourceFilter = null;

                var m_FilterGraph2 = new FilterGraph() as IFilterGraph2;
                hr = m_FilterGraph2.AddSourceFilterForMoniker(vidDev.Mon, null, vidDev.Name, out sourceFilter);
                var pRaw2 = DsFindPin.ByCategory(sourceFilter, PinCategory.Capture, 0);
                var AvailableResolutions = new List<Resolution>();

                VideoInfoHeader v = new VideoInfoHeader();
                IEnumMediaTypes mediaTypeEnum;
                hr = pRaw2.EnumMediaTypes(out mediaTypeEnum);

                AMMediaType[] mediaTypes = new AMMediaType[1];
                IntPtr fetched = IntPtr.Zero;
                hr = mediaTypeEnum.Next(1, mediaTypes, fetched);

                while (fetched != null && mediaTypes[0] != null)
                {
                    Marshal.PtrToStructure(mediaTypes[0].formatPtr, v);
                    if (v.BmiHeader.Size != 0 && v.BmiHeader.BitCount != 0)
                    {
                        if (v.BmiHeader.BitCount > bitCount)
                        {
                            AvailableResolutions.Clear();
                            bitCount = v.BmiHeader.BitCount;
                        }
                        AvailableResolutions.Add(new Resolution(v.BmiHeader.Width, v.BmiHeader.Height));
                    }
                    hr = mediaTypeEnum.Next(1, mediaTypes, fetched);
                }
                return AvailableResolutions;
            }
            catch (Exception ex)
            {
                //MessageBox.Show(ex.Message);
                Console.WriteLine(ex.ToString());
                return new List<Resolution>();
            }
        }

Bind camera names and resolutions to the drop-down list. A selection change event will be triggered if you change the index of the drop-down list:

        private void InitCameraSource()
        {
            cbxWebCamSrc.Items.Clear();
            foreach (CameraInfo camera in mDSManager.GetCameras())
            {
                cbxWebCamSrc.Items.Add(camera.Device.Name);
            }

            cbxWebCamSrc.SelectedIndex = 0;
        }
        private void cbxWebCamSrc_SelectedIndexChanged(object sender, EventArgs e)
        {
            picBoxWebCam.Visible = true;
            picBoxWebCam.BringToFront();
            EnableControls(picboxReadBarcode);
            EnableControls(pictureBoxCustomize);

            InitCameraResolution();
        }
        private void InitCameraResolution()
        {
            cbxWebCamRes.Items.Clear();
            foreach (Resolution resolution in mDSManager.GetCameras()[cbxWebCamSrc.SelectedIndex].Resolutions)
            {
                cbxWebCamRes.Items.Add(resolution.ToString());
            }

            cbxWebCamRes.SelectedIndex = 0;
        }

Set the delegate function for receiving the camera frames:

        TaskCompletedCallBack callback = FrameCallback;
        private volatile bool isFinished = true;
        public void FrameCallback(Bitmap bitmap)
        {
            if (isFinished)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    isFinished = false;
                    ReadFromFrame(bitmap);
                    isFinished = true;
                });
        }

        private void ReadFromFrame(Bitmap bitmap)
        {
            UpdateRuntimeSettingsWithUISetting();
            TextResult[] textResults = null;
            int timeElapsed = 0;

            try
            {
                DateTime beforeRead = DateTime.Now;

                textResults = mBarcodeReader.DecodeBitmap(bitmap, "");

                DateTime afterRead = DateTime.Now;
                timeElapsed = (int)(afterRead - beforeRead).TotalMilliseconds;

                if (textResults == null || textResults.Length <= 0)
                {
                    return;
                }

                if (textResults != null)
                {
                    mDSManager.StopCamera();
                    Bitmap tempBitmap = ((Bitmap)(bitmap)).Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
                    this.BeginInvoke(mPostShowFrameResults, tempBitmap, textResults, timeElapsed, null);
                }

            }
            catch (Exception ex)
            {
                this.Invoke(mPostShowFrameResults, new object[] { bitmap, textResults, timeElapsed, ex });
            }
        }

The Screenshots of .NET Webcam Barcode Scanner

dotnet webcam barcode reader

C# decode barcode

Source Code

https://github.com/yushulx/dotnet-webcam-barcode-reader