1. Overview & Getting Started¶
1.1 Introduction¶
duvc-ctl is a C++ library for controlling UVC cameras on Windows through the DirectShow API. The library exposes camera properties (pan, tilt, zoom, exposure, focus) and video properties (brightness, contrast, white balance) without requiring vendor-specific drivers or SDKs.
Supported operations:
Device enumeration and connection
Camera properties: pan, tilt, roll, zoom, exposure, iris, focus, scan mode, privacy, digital zoom, backlight compensation, lamp
Video properties: brightness, contrast, hue, saturation, sharpness, gamma, color enable, white balance, backlight compensation, gain
Hot-plug detection and device change notifications
Vendor-specific extensions (Logitech cameras supported)
Requirements:
Windows 7 SP1 or later (Windows 10/11 recommended)
C++17 compiler: Visual Studio 2019/2022, or MinGW-w64 with GCC 9+
CMake 3.16 or later
DirectShow (included with Windows)
DirectShow integration:
The library wraps Windows DirectShow APIs including ICreateDevEnum (device enumeration), IAMCameraControl (PTZ and camera properties), IAMVideoProcAmp (video properties), and IKsPropertySet (vendor extensions).
1.2 Installation & Setup¶
Prerequisites¶
Compiler: Visual Studio 2019 or later, or MinGW-w64 with GCC 9+
CMake: Version 3.16 or later
Windows SDK: Included with Visual Studio
Git: For cloning the repository
Build from source¶
git clone https://github.com/allanhanan/duvc-ctl.git
cd duvc-ctl
# Configure
cmake -B build -G "Visual Studio 17 2022" -A x64
# Build
cmake --build build --config Release
# Install (optional)
cmake --install build --config Release
CMake configuration options¶
-DDUVC_BUILD_SHARED=ON # Build shared library (default: ON)
-DDUVC_BUILD_STATIC=ON # Build static library (default: ON)
-DDUVC_BUILD_C_API=ON # Build C API for language bindings (default: ON)
-DDUVC_BUILD_CLI=ON # Build command-line tool (default: ON)
-DDUVC_BUILD_TESTS=OFF # Build test suite (default: OFF)
-DDUVC_BUILD_EXAMPLES=OFF # Build examples (default: OFF)
Example with custom options:
cmake -B build -G "Visual Studio 17 2022" -A x64 \
-DDUVC_BUILD_SHARED=ON \
-DDUVC_BUILD_STATIC=OFF \
-DDUVC_BUILD_TESTS=ON
Integration into your project¶
CMake find_package:
find_package(duvc-ctl REQUIRED)
target_link_libraries(your_target PRIVATE duvc-ctl::duvc-ctl)
Manual integration:
target_include_directories(your_target PRIVATE path/to/duvc-ctl/include)
target_link_libraries(your_target PRIVATE path/to/duvc-ctl/lib/duvc-core.lib)
Package managers (Conan planned):
vcpkg:
vcpkg install duvc-ctlConan:
conan install duvc-ctl/2.0.0
Include headers¶
Single convenience header (recommended):
#include <duvc-ctl/duvc.hpp>
Selective headers:
#include <duvc-ctl/core/camera.h>
#include <duvc-ctl/core/device.h>
#include <duvc-ctl/core/result.h>
#include <duvc-ctl/core/types.h>
1.3 Quick Start Examples¶
List devices¶
#include <duvc-ctl/duvc.hpp>
#include <iostream>
int main() {
auto platform = duvc::create_platform_interface();
auto devices_result = platform->list_devices();
if (!devices_result.is_ok()) {
std::cerr << "Failed to list devices: "
<< devices_result.error().description() << "\n";
return 1;
}
const auto& devices = devices_result.value();
std::wcout << "Found " << devices.size() << " cameras\n";
for (const auto& device : devices) {
std::wcout << L" - " << device.name << L"\n";
}
return 0;
}
Read property¶
#include <duvc-ctl/duvc.hpp>
#include <iostream>
int main() {
auto platform = duvc::create_platform_interface();
// Get devices
auto devices_result = platform->list_devices();
if (!devices_result.is_ok() || devices_result.value().empty()) {
std::cerr << "No cameras found\n";
return 1;
}
const auto& devices = devices_result.value();
// Create connection to first device
auto connection_result = platform->create_connection(devices);
if (!connection_result.is_ok()) {
std::cerr << "Failed to connect: "
<< connection_result.error().description() << "\n";
return 1;
}
auto connection = std::move(connection_result.value());
// Get brightness
auto brightness_result = connection->get_video_property(duvc::VidProp::Brightness);
if (brightness_result.is_ok()) {
const auto& setting = brightness_result.value();
std::cout << "Brightness: " << setting.value
<< " (mode: " << (setting.mode == duvc::CamMode::Auto ? "auto" : "manual")
<< ")\n";
} else {
std::cerr << "Error reading brightness: "
<< brightness_result.error().description() << "\n";
}
return 0;
}
Set property with range validation¶
#include <duvc-ctl/duvc.hpp>
#include <iostream>
int main() {
auto platform = duvc::create_platform_interface();
auto devices_result = platform->list_devices();
if (!devices_result.is_ok() || devices_result.value().empty()) {
return 1;
}
auto connection_result = platform->create_connection(devices_result.value());
if (!connection_result.is_ok()) {
return 1;
}
auto connection = std::move(connection_result.value());
// Query range before setting
auto range_result = connection->get_camera_property_range(duvc::CamProp::Pan);
if (range_result.is_ok()) {
const auto& range = range_result.value();
std::cout << "Pan range: " << range.min << " to " << range.max
<< " (step: " << range.step << ")\n";
// Clamp value to valid range
int target_pan = 45;
int clamped = range.clamp(target_pan);
// Set property
duvc::PropSetting setting{clamped, duvc::CamMode::Manual};
auto set_result = connection->set_camera_property(duvc::CamProp::Pan, setting);
if (set_result.is_ok()) {
std::cout << "Pan set to " << clamped << "\n";
} else {
std::cerr << "Failed: " << set_result.error().description() << "\n";
}
}
return 0;
}
Error handling¶
#include <duvc-ctl/duvc.hpp>
#include <iostream>
int main() {
auto platform = duvc::create_platform_interface();
auto devices_result = platform->list_devices();
if (devices_result.is_error()) {
std::cerr << "Error: " << devices_result.error().description() << "\n";
return 1;
}
if (devices_result.value().empty()) {
std::cerr << "No devices found\n";
return 1;
}
auto connection_result = platform->create_connection(devices_result.value());
if (connection_result.is_error()) {
const auto& error = connection_result.error();
std::cerr << "Connection failed: " << error.description() << "\n";
// Handle specific errors
switch (error.code()) {
case duvc::ErrorCode::DeviceNotFound:
std::cerr << "Camera disconnected\n";
break;
case duvc::ErrorCode::DeviceBusy:
std::cerr << "Camera in use by another application\n";
break;
case duvc::ErrorCode::PermissionDenied:
std::cerr << "Check camera permissions in Windows settings\n";
break;
default:
break;
}
return 1;
}
auto connection = std::move(connection_result.value());
// Attempt property operation
duvc::PropSetting setting{-5, duvc::CamMode::Manual};
auto result = connection->set_camera_property(duvc::CamProp::Exposure, setting);
if (result.is_error()) {
const auto& error = result.error();
if (error.code() == duvc::ErrorCode::PropertyNotSupported) {
std::cerr << "Exposure not supported on this camera\n";
} else if (error.code() == duvc::ErrorCode::InvalidValue) {
std::cerr << "Value out of range\n";
} else {
std::cerr << "Error: " << error.description() << "\n";
}
}
return 0;
}
Check device capabilities¶
#include <duvc-ctl/duvc.hpp>
#include <duvc-ctl/core/capability.h>
#include <iostream>
int main() {
auto platform = duvc::create_platform_interface();
auto devices_result = platform->list_devices();
if (!devices_result.is_ok() || devices_result.value().empty()) {
return 1;
}
// Create capabilities object
duvc::DeviceCapabilities caps(devices_result.value());
// Check camera properties
std::cout << "Supported camera properties:\n";
for (const auto& prop : caps.supported_camera_properties()) {
std::cout << " - " << static_cast<int>(prop) << "\n";
}
// Check video properties
std::cout << "\nSupported video properties:\n";
for (const auto& prop : caps.supported_video_properties()) {
std::cout << " - " << static_cast<int>(prop) << "\n";
}
// Check specific property
if (caps.supports_camera_property(duvc::CamProp::Pan)) {
auto cap_result = caps.get_camera_capability(duvc::CamProp::Pan);
if (cap_result.is_ok()) {
const auto& cap = cap_result.value();
std::cout << "\nPan capability:\n"
<< " Range: " << cap.range.min << " to " << cap.range.max << "\n"
<< " Step: " << cap.range.step << "\n"
<< " Default: " << cap.range.default_val << "\n"
<< " Auto mode: " << (cap.supports_auto() ? "yes" : "no") << "\n";
}
}
return 0;
}
Next: See Section 2 for complete API reference.