| 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 | |
| 23 | int 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 | } |