PTP-IP and USB transport
After the camera is reachable, control, live view and media share one PTP session. Framing is the PTP-IP packet; this page covers the sockets and sessions around it.
Two sockets
Section titled “Two sockets”PTP-IP (CIPA DC-005) uses two TCP connections to port 15740:
- Command socket. Send
Init_Command_Requestwith the app’s initiator GUID and name. The camera answersInit_Command_Ackwith a connection number and its own name, orInit_Fail. - Event socket. Send
Init_Event_Requestcarrying that connection number; wait forInit_Event_Ack.
If either step fails, both sockets close. The initiator GUID is stable per install: Nikon bodies key their pairing profiles to it, so a new GUID on every launch would force re-pairing. Once stored it is never replaced.
Transactions
Section titled “Transactions”Every operation, whether a live-view frame, a property read or write, an event poll, a media chunk or a keep-alive, is one serial transaction on the command socket behind a FIFO gate. There is no pipelining. That is why poll cadence and chunk size matter to the live picture (live view).
| Deadline | Value |
|---|---|
| Command transaction | Short whole-transaction deadline; a wedged or dribbling camera cannot hold the gate |
| First live-view frame | 10 s |
| Steady-state live-view frame | 6 s |
| USB first command | Long, because iOS may still be cataloguing a full card |
A blocked socket read cannot be cancelled, so an expired deadline closes the
command socket and recovery takes over. An idle DeviceReady keeps a quiet
command channel warm.
Events
Section titled “Events”The camera pushes events on the event socket (record started 0xC10A, record
complete 0xC108, record interrupted 0xC105, DevicePropChanged 0x4006).
The app drains that socket continuously: an unread channel backs up the
camera’s send buffer and can stall the session deep into a long take. It also
answers Probe_Request there.
Nikon bodies deliver capture events (ObjectAdded, CaptureComplete) and many
DevicePropChanged announcements through the GetEventEx poll (0x941C,
parameter 0 clears the queue) rather than the socket. The app polls it between
live-view frames in every monitor mode. An announcement carries no value, so it
only schedules an authoritative read; a bounded batch is re-read per poll,
oldest first.
Recovery
Section titled “Recovery”A dropped link keeps the monitor on the last frame while bounded, jittered
retries run
(SessionRecovery.swift,
ReconnectBackoff.swift).
When the budget is spent, or reconnects keep succeeding but die young, automatic
recovery stops and the operator chooses. Rapid session churn can wedge a Nikon
body until its battery is pulled, so the app sends CloseSession, waits a
settle interval before a fresh Init, and never probes a just-dropped camera
with an extra handshake. Background:
connection reliability audit.
The same PTP operations, properties and datasets ride PIMA 15740 generic containers on the USB bulk pipe.
| Platform | USB path |
|---|---|
| iOS / iPadOS | ImageCaptureCore: ICDeviceBrowser discovery and authorization, requestSendPTPCommand for each whole transaction, ptpEventHandler into the event drain |
| Android | USB Host finds a complete PTP interface, asks per-device consent, and hands raw bulk and interrupt bytes to the Swift facade, which owns container framing and the session |
Differences from Wi-Fi:
GetDeviceInfois sent beforeOpenSession. On iOS, ImageCaptureCore often has the session open already;Session_Already_Open(0x201E) counts as success.- There is no pairing. The pairing operations are absent from the USB operation set; the cable is the trust boundary.
- The ZR boots USB sessions in PC-camera mode and refuses app control until
ChangeCameraModeremote mode is set. The body then shows its connected to computer screen. - Saved USB cameras use a local
usb:device key, never a raw USB serial or a network address, and reconnect silently when plugged in.
The CameraTransport boundary
Section titled “The CameraTransport boundary”Wi-Fi and USB share one session layer behind the transaction-level
CameraTransport protocol
(CameraTransport.swift).
One call runs a whole transaction: request, optional data-out, any data-in, and
the response. It cannot sit lower, because ImageCaptureCore only exposes whole
transactions on iOS. Conformers assign transaction IDs and serialize
internally.
| Layer | iOS | Android |
|---|---|---|
| Wi-Fi transport | OpenCine Sources/OpenCineNikon/PTPIPTransport.swift (BSD TCP sockets) |
Kotlin socket adapter, Swift facade session |
| USB transport | OpenCine Sources/OpenCineNikon/USBCameraTransport.swift |
Android USB Host adapter, Swift facade session |
| Session orchestration | NativeCameraSession |
OpenCine Sources/OpenCineNikonAndroid/PTPIPClientSession.swift |
Kotlin never builds PTP operations. Decisions live in the portable core; see ARCHITECTURE.md.