1// Set a UVC webcam's "Power Line Frequency" (anti-flicker) control over USB.
2//
3// macOS's camera API (AVFoundation) doesn't expose this control, but the camera
4// accepts a UVC SET_CUR on its default control pipe via IOKit even while the
5// system UVC driver is streaming — which cancels mains-flicker banding in
6// hardware (verified: the scrolling bars disappear the instant it's set).
7//
8// uvc-powerline <value> [vidHex] [pidHex]
9// value: 0 = off, 1 = 50 Hz, 2 = 60 Hz
10//
11// Defaults to the ZS CAMERA (VID 0x328f / PID 0x0072). Exits 0 if the control
12// was accepted by at least one processing-unit entity.
13#include <CoreFoundation/CoreFoundation.h>
14#include <IOKit/IOCFPlugIn.h>
15#include <IOKit/IOKitLib.h>
16#include <IOKit/usb/IOUSBLib.h>
17#include <stdio.h>
18#include <stdlib.h>
19
20#define UVC_SET_CUR 0x01
21#define PU_POWER_LINE_FREQUENCY_CONTROL 0x05
22
23int main(int argc, char** argv) {
24 int value = (argc > 1) ? atoi(argv[1]) : 2;
25 SInt32 vid = (argc > 2) ? (SInt32)strtol(argv[2], NULL, 16) : 0x328f;
26 SInt32 pid = (argc > 3) ? (SInt32)strtol(argv[3], NULL, 16) : 0x0072;
27
28 CFMutableDictionaryRef match = IOServiceMatching(kIOUSBDeviceClassName);
29 CFNumberRef vr = CFNumberCreate(NULL, kCFNumberSInt32Type, &vid);
30 CFNumberRef pr = CFNumberCreate(NULL, kCFNumberSInt32Type, &pid);
31 CFDictionarySetValue(match, CFSTR(kUSBVendorID), vr);
32 CFDictionarySetValue(match, CFSTR(kUSBProductID), pr);
33 CFRelease(vr);
34 CFRelease(pr);
35
36 io_service_t svc = IOServiceGetMatchingService(kIOMainPortDefault, match);
37 if (!svc) {
38 fprintf(stderr, "uvc-powerline: camera %04x:%04x not found\n", vid, pid);
39 return 1;
40 }
41
42 IOCFPlugInInterface** plug = NULL;
43 SInt32 score;
44 IOCreatePlugInInterfaceForService(
45 svc, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plug, &score);
46 IOObjectRelease(svc);
47 if (!plug) return 1;
48
49 IOUSBDeviceInterface** dev = NULL;
50 (*plug)->QueryInterface(plug, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*)&dev);
51 (*plug)->Release(plug);
52 if (!dev) return 1;
53
54 IOReturn r = (*dev)->USBDeviceOpen(dev);
55 if (r != kIOReturnSuccess) {
56 r = (*dev)->USBDeviceOpenSeize(dev);
57 if (r != kIOReturnSuccess) {
58 fprintf(stderr, "uvc-powerline: cannot open device (0x%x)\n", r);
59 (*dev)->Release(dev);
60 return 2;
61 }
62 }
63
64 UInt8 val = (UInt8)value;
65 int ok = 0;
66 for (int entity = 1; entity <= 6; entity++) {
67 IOUSBDevRequest req;
68 req.bmRequestType = USBmakebmRequestType(kUSBOut, kUSBClass, kUSBInterface);
69 req.bRequest = UVC_SET_CUR;
70 req.wValue = (PU_POWER_LINE_FREQUENCY_CONTROL << 8);
71 req.wIndex = (entity << 8) | 0; // VideoControl interface 0
72 req.wLength = 1;
73 req.pData = &val;
74 if ((*dev)->DeviceRequest(dev, &req) == kIOReturnSuccess) ok = 1;
75 }
76
77 (*dev)->USBDeviceClose(dev);
78 (*dev)->Release(dev);
79 return ok ? 0 : 3;
80}