Archives
-
How to Access Webcam Properties from C#
My Logitech C920 webcam requires some tweaking of settings like exposure, gain, focus, etc. to get good image quality. I uninstalled the “feature rich” Logitech software and now change the settings using the bare-bones Windows webcam properties dialog. This works well for me; unfortunately, the settings are not persisted reliably. After a cold-boot, or sometimes after simply starting an application that uses the webcam, I have to make the adjustments again.
That is why I became interested in what would it take to read and write the webcam properties and to open the properties dialog from C#. The result of a web search was a bit intimidating as I came across multimedia APIs that go way beyond what I intended to do. After all, I only wanted to access the properties, not write a full-blown video capture suite.
In the end I settled on DirectShow.Net, a C# wrapper around DirectShow under LPGL license. Even though DirectShow is an old API and the DirectShow.Net project seems to be no longer active, I found one very important reason to use it: A working sample that opened the webcam properties dialog.
This blog post starts with a guided tour through the sample, with the intended side-effect of making the sample easier to discover on the web. Additionally, I will describe how to access the properties from your code.
Step 1: Download DirectShow.Net
- Visit http://directshownet.sourceforge.net/
- Go to “Downloads”
- Download the latest version of the library (
DirectShowLibV2-1.zip
at the time of this writing) - Download the samples (
DirectShowSamples-2010-February.zip
) - Unpack the ZIP files so that the folder
Samples
is in the same directory asDocs
,lib
andsrc
. - The
lib
folder contains the fileDirectShowLib-2005.dll
which the samples reference.
Step 2: Run the “DxPropPages” demog
- Open
Samples\Capture\DxPropPages\DxPropPages-2008.sln
in Visual Studio and let the “One-way upgrade” finish. - Run the project.
- In the window that appears,
- select your webcam and
- press the “Show property pages” button.
- On my computer, a properties dialog with two tabs appears. Depending on your drivers/webcam software, the dialog may have been replaced. But the generic, low-level dialog looks like this:
(Remember “Video Proc Amp” and “Camera Control”, we will come across these names later)
Step 3: Look at the code
- Stop the program.
- Open the code view of the file
Form1.cs
. - Set a breakpoint at the start of
- the constructor e
Form1()
, - the
comboBox1_SelectedIndexChanged()
event handler, and - the
DisplayPropertyPage()
method. - Run the program in the debugger.
How to get the available webcam(s)
When the first breakpoint is hit, you will see the following lines:
foreach (DsDevice ds in DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice)) { comboBox1.Items.Add(ds.Name); }
The code gets all available “video input devices” (which include webcams) and fills the dropdown that you used in step 2 to choose your webcam.
A
DsDevice
instance has two important properties for identifying a device:Name
returns a human-readable name (i.e., what you saw in the dropdown list)DevicePath
returns a unique identifier.
At this point, the sample does not store the instances, only the names, even though we need the
DsDevice
instance for the selected webcam later. I am not sure whether there is a reason for this other than keeping the sample code short and to be able to re-use theCreateFilter()
method (which we will look at soon).How to open the properties dialog
Now continue to run the program. The
comboBox1_SelectedIndexChanged
event handler gets called automatically during startup. If your webcam is not the first device, let the program continue and select the webcam in the dropdown.After the breakpoint has been hit, look at the code.
- The purpose of the event handler is to set the field
theDevice
(of typeIBaseFilter
) which we need later. - The call of
Marshal.ReleaseComObject(theDevice)
when switching between devices is a reminder that we are dealing with COM and its reference counting (instead of relying on garbage collection). - Note that the variable name
devicepath
is misleading; the dropdown contains the display names of the devices. This becomes clear when we look at theCreateFilter()
method: The second parameter is calledfriendlyname
which is more appropriate.
Inside the
CreateFilter()
method, some “COM stuff” happens. The important bit for us is that the returnedIBaseFilter
is assigned to the fieldtheDevice
, which is used in thebutton1_Click
handler when callingDisplayPropertyPage()
.The method
DisplayPropertyPage()
contains even more COM stuff that we can ignore for now, because the method does exactly what its name says. We will see later that we need some basic understanding what is happening inside, though.How to make the controls in the dialog appear “Windows 10”-like
The steps described my blog post “Windows 10 Theme for a Properties Dialog” for a WPF application are also valid for WinForms. In the case of the sample application the change also affects the controls of the main window.
Step 4: Start to experiment
The code inside
DisplayPropertyPage()
uses theISpecifyPropertyPages
interface. Two other interesting interfaces areIAMVideoProcAmp
andIAMCameraControl
. The names correspond to the pages of the properties dialog. Using the two interfaces, you can access the properties you see in the dialog.How to read or write the webcam properties from your code
The interfaces
IAMVideoProcAmp
andIAMCameraControl
both offerGetRange()
,Get()
andSet()
methods.For
IAMCameraControl
, these methods are defined like this:int GetRange( [In] CameraControlProperty Property, [Out] out int pMin, [Out] out int pMax, [Out] out int pSteppingDelta, [Out] out int pDefault, [Out] out CameraControlFlags pCapsFlags ); int Set( [In] CameraControlProperty Property, [In] int lValue, [In] CameraControlFlags Flags ); int Get( [In] CameraControlProperty Property, [Out] out int lValue, [Out] out CameraControlFlags Flags );
When using the methods:
- You specify the property you want to access via an enum value of type
CameraControlProperty
. Your device may not support all properties, though – if you look at the screenshots above, you will notice that some sliders are disabled. Therefore it is important to check the return value to be 0 (zero) for a successful call. - The
CameraControlFlags
value contains information whether the property is (or should be) set automatically and / or manually.
Let us say you want to access the “exposure” property of your webcam (this may or may not work on your webcam; if not, you can try another property).
For a quick-and-dirty test, resize the “Show property pages” button so can add another button next to it, double click the new button and insert the following code into the “Click” event handler:
var cameraControl = theDevice as IAMCameraControl; if (cameraControl == null) return; cameraControl.GetRange(CameraControlProperty.Exposure, out int min, out int max, out int steppingDelta, out int defaultValue, out var flags); Debug.WriteLine($"min: {min}, max: {max}, steppingDelta: {steppingDelta}"); Debug.WriteLine($"defaultValue: {defaultValue}, flags: {flags}");
When I run the program, select my Logitech C920 webcam and press the button I added above, the following appears in the debug output window in Visual Studio:
min: -11, max: -2, steppingDelta: 1 defaultValue: -5, flags: Auto, Manual
This means that the exposure can be adjusted from -11 to -2, with -5 being the default. The property supports both automatic and manual mode.
Not all properties have a stepping delta of 1. For the Logitech C920, for instance, the focus property (
CameraControlProperty.Focus
) has a range from 0 to 250 with a stepping delta of 5. This is why setting the property value to e.g. 47 has the same effect on the hardware as setting the value to 45.Calling the
Get()
andSet()
methods is simple. For instance, setting the focus to a fixed value of 45 looks like this:cameraControl.Set(CameraControlProperty.Focus, 45, CameraControlFlags.Manual);
The
CameraControlFlags.Manual
tells the webcam to switch off aufo-focus.Where to go from here
Note the COM pitfall in the sample
If you are as inexperienced working with COM interop as I am and look at the original sample code inside
DisplayPropertyPage()
, you may notice that the lineISpecifyPropertyPages pProp = dev as ISpecifyPropertyPages;
seems to have a corresponding
Marshal.ReleaseComObject(pProp);
Does this mean that we need a similar call in our experimental code we added above?
No, because if you add the (only supposedly) “missing”
Marshal.ReleaseComObject(cameraControl)
to your code and click the button repeatedly, you will run into this exception:System.Runtime.InteropServices.InvalidComObjectException HResult=0x80131527 Message=COM object that has been separated from its underlying RCW cannot be used. Source=DxPropPages …
What is happening here? The answer is that simply “casting” to a COM interface in C# is not something that has to be “cleaned up”. The code may imply that, but you could change the line
Marshal.ReleaseComObject(pProp);
to
Marshal.ReleaseComObject(dev); // oDevice would work, too
and it still would run without leaking references.
How do I know? Because
Marshal.ReleaseComObject()
returns the new reference count and changing the line toDebug.WriteLine(Marshal.ReleaseComObject(oDevice));
will output 1 each time we opened and close the properties dialog. The value of 1 is correct, because want to continue to be able to access the device object.
Placing a copy of that line in front of the call of the external function
OleCreatePropertyFrame()
obviously does not make sense and will lead to an exception. But if you do it anyway, just for testing, the debug output will show 0 instead of 1. This shows us that passing the object as a parameter in COM interop – at least in this case – caused the reference count to be increased. This is whyMarshal.ReleaseComObject()
is called afterOleCreatePropertyFrame()
, not because of the cast toISpecifyPropertyPages
.Practice defensive coding
As already mentioned, not all webcams support all properties. And if a webcam supports a property, the allowed values may differ from other devices. That is why you should use
GetRange()
to determine- whether a property is supported (return value 0),
- the range of the allowed values, and
- whether the property can be set to “auto”.
Last, but not least: When you access a USB webcam – like any other detachable device – be prepared for it not being available. Not only at startup, but also while your program is running, because the device could have been unplugged unexpectedly.