macOS Multi-Camera USB Challenges
Canon EDSDK, libgphoto2, and the ptpcamerad Problem
Connecting a Canon and a Nikon camera simultaneously on macOS seems straightforward — until macOS’s PTP daemon silently claims every camera on the USB bus and refuses to let go.
TL;DR
On macOS, Canon’s EDSDK uses Apple’s ImageCaptureCore framework which activates ptpcamerad — a system daemon that claims exclusive USB access to all PTP cameras. Nikon’s libgphoto2 backend uses libusb, which can’t claim a device already held by ptpcamerad. System Integrity Protection prevents disabling the daemon. The workaround: connect Nikon before Canon, and don’t disconnect once both are connected.
The Setup
eclipseClick supports multi-camera operation — connect two camera bodies simultaneously for redundancy or different focal lengths during totality. On Windows, this works seamlessly: Canon talks to EDSDK, Nikon talks to the MAID SDK, both coexist on the USB bus without issues.
On macOS, each vendor uses a different USB access strategy:
| Vendor | macOS SDK | USB Access Method | ptpcamerad |
|---|---|---|---|
| Canon | EDSDK (x64 via Rosetta) | ImageCaptureCore | Cooperates |
| Nikon | libgphoto2 (arm64 native) | libusb | Conflicts |
| Sony | Camera Remote SDK / libgphoto2 | Vendor SDK or libusb | Conflicts (gphoto2 path) |
The Problem: ptpcamerad
When a PTP camera is connected via USB, macOS automatically launches ptpcamerad — a system daemon that claims exclusive access to the camera’s USB interface. This is the same daemon that powers Image Capture, Photos, and Preview’s camera import features.
Canon’s EDSDK uses Apple’s ImageCaptureCore framework, which cooperates with ptpcamerad. EDSDK and ptpcamerad share the USB device peacefully. When EDSDK initializes, it activates ptpcamerad for all PTP cameras on the bus — not just the Canon.
Nikon’s libgphoto2 backend uses libusb for direct USB access. When libgphoto2 calls gp_camera_init(), it tries to claim the USB interface via libusb_claim_interface(). But ptpcamerad already holds the interface, and macOS returns Access Denied.
The conflict
Canon EDSDK activates ptpcamerad → ptpcamerad claims ALL PTP cameras → libgphoto2 can’t claim Nikon → gphoto2 error -53: Could not claim the USB device
What We Tried
1. Kill ptpcamerad before connecting
We added killall ptpcamerad right before gp_camera_init() inside the C++ bridge process, with up to 5 retry attempts. This works after a fresh reboot (when ptpcamerad hasn’t fully established its claim), but fails once Canon has been connected — ptpcamerad respawns in under 100ms and reclaims the device before libusb can grab it.
2. Disable ptpcamerad via launchctl
launchctl bootout returns “Operation not permitted while System Integrity Protection is engaged”. SIP protects ptpcamerad as a system service. Disabling SIP is not acceptable for end users.
3. Force USB device reset via libusb
We tried libusb_set_auto_detach_kernel_driver() followed by libusb_claim_interface(). macOS returns LIBUSB_ERROR_ACCESS (-3) — even with kernel driver auto-detach enabled, SIP prevents user-space detachment of system-owned kernel drivers.
4. IOKit USB re-enumerate
We attempted to force a USB device re-enumerate via IOKit to reset ptpcamerad’s claim. This requires entitlements that unsigned apps don’t have, and even with entitlements, ptpcamerad reclaims immediately.
What Works
The key insight: connection order matters. If libgphoto2 claims the Nikon’s USB interface before ptpcamerad is activated, ptpcamerad cannot take it away. Canon’s EDSDK can then connect separately via ImageCaptureCore without conflict.
Recommended multi-camera workflow on macOS
- Reboot the Mac before the eclipse (fresh ptpcamerad state)
- Connect Nikon/Sony first — libgphoto2 claims the USB interface before ptpcamerad
- Run FPS tests on Nikon while it’s the only camera connected
- Connect Canon second — EDSDK activates ptpcamerad, but Nikon’s claim is already held
- Run Canon FPS tests
- Do not disconnect either camera — once both are armed, go straight to execution
The DSUSB Complication
DSUSB (Digital Shoestring USB) triggers the camera shutter via a physical cable connected to the remote release port. While gphoto2 holds the PTP session, the Nikon enters “PC control” mode and ignores the physical remote trigger.
Our solution: the bridge releases the PTP session (gp_camera_exit()) during the DSUSB trigger window, allowing the camera to respond to the physical shutter. After the burst, it reconnects PTP (gp_camera_init()) to read the shot count.
This works perfectly in single-camera setups. In multi-camera setups with Canon, the PTP reconnect may fail (ptpcamerad grabs the device). eclipseClick detects this and shows a “unplug and replug” message. For the eclipse itself, DSUSB tests should be completed before connecting the Canon.
Other macOS Fixes in This Release
- Canon detection retry — increased from 3s to 10s on macOS. EDSDK’s ImageCaptureCore device discovery is asynchronous and takes several seconds under Rosetta.
- Connect timeout — increased from 15s to 30s on macOS. Canon EDSDK under Rosetta takes 20–27s for the first connection.
- Capture timeout — increased from 10s to 30s. gphoto2 PTP first capture has session setup overhead (~10–15s on Nikon D7200).
- Settings flood prevention — deduplicated concurrent
getAllSettingscalls when property-change events fire rapidly.
Windows vs macOS: Why It’s Different
On Windows, there is no equivalent of ptpcamerad. Each camera SDK (Canon EDSDK, Nikon MAID SDK, Sony CrSDK) directly accesses the USB device through its own driver. Multiple SDKs can coexist on the same bus without conflict. Multi-camera operation “just works.”
macOS’s centralized PTP daemon architecture is designed for consumer workflows (import photos via Image Capture), not for simultaneous multi-vendor camera control. It’s a design tradeoff that works well for most users but creates friction for specialized tools like eclipseClick.
Summary
| Single Canon | Works perfectly |
| Single Nikon | Works perfectly (including DSUSB) |
| Canon + Nikon | Works if Nikon connects first; avoid disconnecting |
| 2x Canon | Works perfectly |
| 2x Nikon | Works if both connect before ptpcamerad |
| Windows (any combo) | Works perfectly |