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.
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;
DsError.ThrowExceptionForHR(hr);
Attach the filter graph to the capture graph
hr = this.captureGraphBuilder.SetFiltergraph(this.graphBuilder);
DsError.ThrowExceptionForHR(hr);
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);
DsError.ThrowExceptionForHR(hr);
// The device enumerator is no more needed
Marshal.ReleaseComObject(devEnum);
// 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);
}
else
{
throw new ApplicationException("Unable to access video capture device!");
}
// Release COM objects
Marshal.ReleaseComObject(moniker[0]);
Marshal.ReleaseComObject(classEnum);
// An exception is thrown if cast fail
return (IBaseFilter)source;
Add camera source to graph
hr = this.graphBuilder.AddFilter(sourceFilter, "Video Capture");
DsError.ThrowExceptionForHR(hr);
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);
DsError.ThrowExceptionForHR(hr);
DsUtils.FreeAMMediaType(media);
media = null;
hr = sampleGrabber.SetCallback(this, 1);
DsError.ThrowExceptionForHR(hr);
}
Add the filter to graph:
hr = this.graphBuilder.AddFilter(sampleGrabber as IBaseFilter, "Frame Callback");
DsError.ThrowExceptionForHR(hr);
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);
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 preview
hr = this.captureGraphBuilder.RenderStream(PinCategory.Preview, MediaType.Video, sourceFilter, (sampleGrabber as IBaseFilter), null);
DsError.ThrowExceptionForHR(hr);
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);
DsError.ThrowExceptionForHR(hr);
hr = this.videoWindow.put_WindowStyle(WindowStyle.Child);
DsError.ThrowExceptionForHR(hr);
// Make the video window visible, now that it is properly positioned
hr = this.videoWindow.put_Visible(OABool.True);
DsError.ThrowExceptionForHR(hr);
// Set the video position
Rectangle rc = pictureBox1.ClientRectangle;
hr = videoWindow.SetWindowPosition(0, 0, _previewWidth, _previewHeight);
DsError.ThrowExceptionForHR(hr);
Start webcam preview
rot = new DsROTEntry(this.graphBuilder);
hr = this.mediaControl.Run();
DsError.ThrowExceptionForHR(hr);
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);
v.RotateFlip(RotateFlipType.Rotate180FlipX);
if (isFinished)
{
this.BeginInvoke((MethodInvoker)delegate
{
isFinished = false;
ReadBarcode(v);
isFinished = true;
});
}
return 0;
}
Source Code
https://github.com/yushulx/DirectShow.NET-Webcam-Barcode-Reader