OBSBOT Meet 2 protocol

This is the human-readable view of the audit trail at doc/protocol/meet2/. Every selector value and byte layout in crates/libobsbot-core/src/devices/meet2.rs must be backed by a committed .pcapng there, with a matching .md describing the bytes. The sourcing rule spells out what inputs are permitted.

Camera-level identifiers

Read from lsusb -v -d 3564:fefb against the unit used for capture. The full descriptor dump is in descriptors.txt.

USB vendor id0x3564 (Remo Tech Co., Ltd.)
USB product id (Meet 2)0xfefb
bcdDevice5.10
XU GUID{9a1e7291-6843-4683-6d92-39bc7906ee49}
VideoControl interface0
XU entity id2
XU control count7
Serial stringnot exposed in descriptor (iSerial = 0); reported by camera at runtime as RMOMWYI1141LCV
Firmware (observed)4.4.6.1

The raw GUID bytes in the descriptor are 91 72 1e 9a 43 68 83 46 6d 92 39 bc 79 06 ee 49 - Microsoft encoding (first three fields little-endian, last eight bytes big-endian), which is what the UVC class expects when matching the XU.

Control-surface split

The camera exposes two control surfaces, only one of which is OBSBOT-specific. This came out of the first capture of OBSBOT's libdev.so driving the device:

SurfaceEntityLives here
Standard UVC CameraTerminal 1 zoom, pan/tilt, focus, exposure, roll - UVC 1.5 §A.9.4 selectors
Standard UVC ProcessingUnit 3 brightness, contrast, saturation, hue, sharpness, gain, WB temp + auto - UVC 1.5 §A.9.5 selectors
OBSBOT vendor extension 2 mediaMode, HDR/WDR, FOV preset, face AE/focus, AI tracking, status - all proprietary

Standard UVC paths do not require per-method .pcapng files - UVC 1.5 §A.9 is the source. OBSBOT XU paths do.

OBSBOT XU framing

Two distinct payload styles are visible on the XU during the first capture.

Selector 0x06 - mode register

SET_CUR writes a 60-byte payload [control_id, value_size, value..., 0×padding]. The control id selects which OBSBOT-proprietary setting to update; per-method captures under doc/protocol/meet2/ pin the wire bytes for each.

Control idMethodValue size
0x00media mode1 byte (Normal=0, Background=1, AutoFrame=2)
0x01WDR / HDR1 byte (off=0, Dol2To1=1)
0x03face AE1 byte (off=0, on=1)
0x04FOV preset1 byte (Wide=0, Medium=1, Narrow=2)
0x0dauto-framing sub-mode2 bytes [group_single, close_upper]
0x16AI master mode2 bytes u16 LE (None=0, Group=1, Human=2, Hand=3, WhiteBoard=4, Desk=5)

GET_CUR on the same selector returns a 60-byte status blob with a fixed marker byte (0x27 on firmware 4.4.6.1) and live readback of WDR (offset 0x06), face-AE (offset 0x07), and AI / auto-framing / media mode (offset 0x18, shared because the camera collapses those three settings into a single internal enum). The full offset table is in statusBlob.md.

Selector 0x02 - RPC-framed channel

SET_CUR and GET_CUR always with wLength = 60. Payload has a recurring header:

offset 0:     0xAA              magic
offset 1:     seq               low 10 bits forced to 0x1AA by libdev;
                                bits 5-6 toggle the inner CRC below
offset 2:     sub-seq           per-message in a session
offset 3:     0x00              reserved
offset 4-5:   0x0C 0x00         outer length (12, u16 LE) - the CRC
                                below covers exactly this many bytes
offset 6-7:   outer CRC         CRC-16/USB over buf[0..outer_len] with
                                this field zeroed; recovered from a
                                libdev.so disassembly, see
                                crc-investigation.md
offset 8:     0x0A              request direction marker
offset 9:     cmd_set
offset 10:    cmd_id
offset 11:    sub_cmd_id
offset 12-13: inner length      payload byte count, u16 LE
offset 14-15: inner CRC         CRC-16/USB over buf[12..16+inner_len]
                                with this field zeroed; only present
                                when (seq & 0x60) != 0
offset 16..   payload
padded to 60 bytes with 0x00

The recovered CRC algorithm lets libobsbot synthesise arbitrary RPC frames at runtime. The remaining device-specific input is the 6-byte MAC tail the camera embeds in some command payloads; it's learned once at Devices::open via the (cmd_set 0x0D, cmd_id 0x08, sub-cmd 0x18) handshake (the one RPC frame that doesn't itself need a MAC).

Capture procedure

  1. On Linux: run0 modprobe usbmon && run0 setfacl -m u:$USER:r /dev/usbmon*.
  2. Plug in the Meet 2 and note bus/device with lsusb | grep -i obsbot.
  3. Launch Wireshark / dumpcap on usbmon<bus> with capture filter usb.device_address == <dev>.
  4. Run an OBSBOT GUI/CLI built against libdev.so.
  5. Manipulate only the control under test - e.g. slide brightness to min, mid, max in sequence.
  6. Stop the capture, save it under doc/protocol/meet2/ as <methodName>.pcapng.
  7. In Wireshark, filter usb.bmRequestType == 0x21 && usb.setup.bRequest == 0x01 for SET_CUR and 0xA1 && 0x81 for GET_CUR.
  8. Record findings in <methodName>.md.