The open source OpenXR runtime
at main 485 lines 14 kB view raw
1/* 2 * Copyright 2021, Collabora, Ltd. 3 * Copyright 2022 Jan Schmidt 4 * SPDX-License-Identifier: BSL-1.0 5 * 6 */ 7 8/*! 9 * @file 10 * @brief Oculus Rift S camera handling 11 * 12 * The Rift S camera module, handles reception and dispatch 13 * of camera frames. 14 * 15 * @author Jan Schmidt <jan@centricular.com> 16 * @ingroup drv_rift_s 17 */ 18#include <string.h> 19 20#include "rift_s.h" 21#include "rift_s_camera.h" 22 23#include "os/os_threading.h" 24#include "xrt/xrt_byte_order.h" 25 26#include "xrt/xrt_defines.h" 27#include "xrt/xrt_frame.h" 28#include "xrt/xrt_frameserver.h" 29 30#include "util/u_autoexpgain.h" 31#include "util/u_debug.h" 32#include "util/u_var.h" 33#include "util/u_sink.h" 34#include "util/u_frame.h" 35#include "util/u_trace_marker.h" 36 37#define DEFAULT_EXPOSURE 6000 38#define DEFAULT_GAIN 127 39 40#define RIFT_S_MIN_EXPOSURE 38 41#define RIFT_S_MAX_EXPOSURE 14022 42 43#define RIFT_S_MIN_GAIN 16 44#define RIFT_S_MAX_GAIN 255 45 46//! Specifies whether the user wants to enable autoexposure from the start. 47DEBUG_GET_ONCE_BOOL_OPTION(rift_s_autoexposure, "RIFT_S_AUTOEXPOSURE", true) 48 49struct rift_s_camera 50{ 51 struct os_mutex lock; 52 53 struct rift_s_tracker *tracker; 54 55 struct rift_s_camera_calibration_block *camera_calibration; 56 57 struct xrt_frame_sink in_sink; // Receive raw frames and split them 58 59 struct u_sink_debug debug_sinks[2]; 60 61 rift_s_camera_report_t camera_report; 62 63 uint16_t last_slam_exposure, target_exposure; 64 uint8_t last_slam_gain, target_gain; 65 66 bool manual_control; //!< Whether to control exp/gain manually or with aeg 67 struct u_var_draggable_u16 exposure_ui; //! Widget to control `exposure` value 68 struct u_autoexpgain *aeg; 69}; 70 71struct rift_s_camera_finder 72{ 73 const char *hmd_serial_no; 74 75 struct xrt_fs *xfs; 76 struct xrt_frame_context *xfctx; 77}; 78 79union rift_s_frame_data { 80 struct 81 { 82 uint8_t frame_type; // 0x06 or 0x86 (controller or SLAM exposure) 83 __le16 magic_abcd; // 0xabcd 84 __le16 frame_ctr; // Increments every exposure 85 __le32 const1; // QHWH 86 uint8_t pad1[7]; // all zeroes padding to 16 bytes 87 __le64 frame_ts; // microseconds 88 __le32 frame_ctr2; // Another frame counter, but only increments on alternate frames @ 30Hz 89 __le16 slam_exposure[5]; // One 16-bit per camera. Exposure duration? 90 uint8_t pad2[2]; // zero padding 91 uint8_t slam_gain[5]; // One byte per camera. 0x40 or 0xf0 depending on frame type 92 uint8_t pad3; // zero padding 93 __le16 unknown1; // changes every frame. No clear pattern 94 __le16 magic_face; // 0xface 95 96 } __attribute__((packed)) data; 97 uint8_t raw[50]; 98}; 99 100static void 101update_expgain(struct rift_s_camera *cam, struct xrt_frame *xf); 102 103static void 104receive_cam_frame(struct xrt_frame_sink *sink, struct xrt_frame *xf); 105 106static void 107on_video_device(struct xrt_prober *xp, 108 struct xrt_prober_device *pdev, 109 const char *product, 110 const char *manufacturer, 111 const char *serial, 112 void *ptr) 113{ 114 struct rift_s_camera_finder *finder = (struct rift_s_camera_finder *)ptr; 115 116 /* Already found a device? */ 117 if (finder->xfs != NULL) 118 return; 119 120 if (product == NULL || manufacturer == NULL || serial == NULL) { 121 return; 122 } 123 124 RIFT_S_TRACE("Inspecting video device %s - %s serial %s", manufacturer, product, serial); 125 126 if ((strcmp(product, "Rift S Sensor") == 0) && (strcmp(manufacturer, "Oculus VR") == 0)) { 127 // && (strcmp(serial, finder->hmd_serial_no) == 0)) { 128 // Serial no seems to be all zeros right now, so ignore it 129 xrt_prober_open_video_device(xp, pdev, finder->xfctx, &finder->xfs); 130 return; 131 } 132} 133 134struct rift_s_camera * 135rift_s_camera_create(struct xrt_prober *xp, 136 struct xrt_frame_context *xfctx, 137 const char *hmd_serial_no, 138 struct os_hid_device *hid, 139 struct rift_s_tracker *tracker, 140 struct rift_s_camera_calibration_block *camera_calibration) 141{ 142 struct rift_s_camera_finder finder = { 143 0, 144 }; 145 146 DRV_TRACE_MARKER(); 147 148 /* Set up the finder with the HMD serial number and frame server context we want */ 149 finder.xfctx = xfctx; 150 finder.hmd_serial_no = hmd_serial_no; 151 152 /* Re-probe devices. The v4l2 camera device should have appeared by now */ 153 int retry_count = 5; 154 do { 155 xrt_result_t xret = xrt_prober_probe(xp); 156 157 if (xret != XRT_SUCCESS) { 158 return NULL; 159 } 160 161 xrt_prober_list_video_devices(xp, on_video_device, &finder); 162 if (finder.xfs != NULL) { 163 break; 164 } 165 166 /* Sleep 1 second before retry */ 167 os_nanosleep((uint64_t)U_TIME_1S_IN_NS); 168 } while (retry_count-- > 0); 169 170 if (finder.xfs == NULL) { 171 RIFT_S_ERROR("Didn't find Rift S camera device"); 172 return NULL; 173 } 174 175 struct rift_s_camera *cam = U_TYPED_CALLOC(struct rift_s_camera); 176 177 if (os_mutex_init(&cam->lock) != 0) { 178 RIFT_S_ERROR("Failed to init camera configuration mutex"); 179 goto cleanup; 180 } 181 182 // Store the tracker 183 cam->tracker = tracker; 184 cam->camera_calibration = camera_calibration; 185 186 /* Configure default camera settings */ 187 rift_s_protocol_camera_report_init(&cam->camera_report); 188 cam->camera_report.uvc_enable = 0x1; 189 cam->camera_report.radio_sync_flag = 0x1; 190 191 /* Store the defaults from the init() call into our current settings */ 192 cam->last_slam_exposure = cam->camera_report.slam_frame_exposures[0]; 193 cam->last_slam_gain = cam->camera_report.slam_frame_gains[0]; 194 195 cam->target_exposure = DEFAULT_EXPOSURE; 196 cam->target_gain = DEFAULT_GAIN; 197 198 rift_s_camera_update(cam, hid); 199 200 cam->in_sink.push_frame = receive_cam_frame; 201 202 bool enable_aeg = debug_get_bool_option_rift_s_autoexposure(); 203 int frame_delay = 2; // Exposure updates take effect on the 2nd frame after sending 204 cam->aeg = u_autoexpgain_create(U_AEG_STRATEGY_TRACKING, enable_aeg, frame_delay); 205 206 u_sink_debug_init(&cam->debug_sinks[0]); 207 u_sink_debug_init(&cam->debug_sinks[1]); 208 209 struct xrt_frame_sink *tmp = &cam->in_sink; 210 211 struct xrt_fs_mode *modes = NULL; 212 uint32_t count; 213 214 xrt_fs_enumerate_modes(finder.xfs, &modes, &count); 215 216 bool found_mode = false; 217 uint32_t selected_mode = 0; 218 219 for (; selected_mode < count; selected_mode++) { 220 if (modes[selected_mode].format == XRT_FORMAT_YUYV422) { 221 found_mode = true; 222 break; 223 } 224 if (modes[selected_mode].format == XRT_FORMAT_MJPEG) { 225 u_sink_create_format_converter(xfctx, XRT_FORMAT_L8, tmp, &tmp); 226 found_mode = true; 227 break; 228 } 229 } 230 231 if (!found_mode) { 232 selected_mode = 0; 233 RIFT_S_ERROR("Couldn't find compatible camera input format."); 234 goto cleanup; 235 } 236 237 free(modes); 238 239 u_var_add_root(cam, "Oculus Rift S Cameras", true); 240 241 u_var_add_bool(cam, &cam->manual_control, "Manual exposure and gain control"); 242 cam->exposure_ui.val = &cam->target_exposure; 243 cam->exposure_ui.min = RIFT_S_MIN_EXPOSURE; 244 cam->exposure_ui.max = RIFT_S_MAX_EXPOSURE; 245 cam->exposure_ui.step = 25; 246 247 u_var_add_draggable_u16(cam, &cam->exposure_ui, "Exposure"); 248 u_var_add_u8(cam, &cam->target_gain, "Gain"); 249 u_var_add_gui_header(cam, NULL, "Auto exposure and gain control"); 250 u_autoexpgain_add_vars(cam->aeg, cam, ""); 251 252 u_var_add_gui_header(cam, NULL, "Camera Streams"); 253 u_var_add_sink_debug(cam, &cam->debug_sinks[0], "Tracking Streams"); 254 u_var_add_sink_debug(cam, &cam->debug_sinks[1], "Controller Streams"); 255 256 /* Finally, start the video feed */ 257 xrt_fs_stream_start(finder.xfs, tmp, XRT_FS_CAPTURE_TYPE_TRACKING, selected_mode); 258 259 return cam; 260 261cleanup: 262 rift_s_camera_destroy(cam); 263 return NULL; 264} 265 266void 267rift_s_camera_destroy(struct rift_s_camera *cam) 268{ 269 u_var_remove_root(cam); 270 os_mutex_destroy(&cam->lock); 271 free(cam); 272} 273 274static bool 275parse_frame_data(const struct xrt_frame *xf, union rift_s_frame_data *row_data) 276{ 277 /* Parse out the bits encoded as 8x8 blocks in the top rows */ 278 unsigned int x, out_x; 279 280 if (xf->width != 50 * 8 * 8 || xf->height < 8) 281 return false; 282 283 uint8_t *pix = &xf->data[xf->width * 4]; 284 285 int bit = 7; 286 for (x = 4, out_x = 0; x < xf->width; x += 8) { 287 uint8_t val = 0; 288 if (pix[x] > 128) 289 val = 1 << bit; 290 291 if (bit == 7) { 292 row_data->raw[out_x] = val; 293 } else { 294 row_data->raw[out_x] |= val; 295 } 296 if (bit > 0) 297 bit--; 298 else { 299 bit = 7; 300 out_x++; 301 } 302 } 303 304 /* Check magic numbers */ 305 if (__le16_to_cpu(row_data->data.magic_abcd) != 0xabcd) 306 return false; 307 if (__le16_to_cpu(row_data->data.magic_face) != 0xface) 308 return false; 309 310 return true; 311} 312 313static int 314get_y_offset(struct rift_s_camera *cam, enum rift_s_camera_id cam_id, union rift_s_frame_data *row_data) 315{ 316 /* There's a magic formula for computing the vertical offset of each camera view 317 * based on exposure, due to some internals of the headset. This formula extracted 318 * through trial and error */ 319 int exposure = __le16_to_cpu(row_data->data.slam_exposure[cam_id]); 320 int y_offset = (exposure + 275) / 38; 321 322 if (y_offset > 375) { 323 y_offset = 375; 324 } else if (y_offset < 8) { 325 y_offset = 8; 326 } 327 328 return y_offset; 329} 330 331static struct xrt_frame * 332rift_s_camera_extract_frame(struct rift_s_camera *cam, 333 enum rift_s_camera_id cam_id, 334 struct xrt_frame *full_frame, 335 union rift_s_frame_data *row_data) 336{ 337 struct rift_s_camera_calibration *calib = &cam->camera_calibration->cameras[cam_id]; 338 struct xrt_rect roi = calib->roi; 339 340 roi.offset.h = get_y_offset(cam, cam_id, row_data); 341 342 struct xrt_frame *xf_crop = NULL; 343 344 u_frame_create_roi(full_frame, roi, &xf_crop); 345 346 return xf_crop; 347} 348 349static void 350receive_cam_frame(struct xrt_frame_sink *sink, struct xrt_frame *xf) 351{ 352 struct rift_s_camera *cam = container_of(sink, struct rift_s_camera, in_sink); 353 bool release_xf = false; 354 355 RIFT_S_TRACE("cam img t=%" PRIu64 " source_t=%" PRIu64, xf->timestamp, xf->source_timestamp); 356 357 // If the format is YUYV422 we need to override it to L8 and double the width 358 // because the v4l2 device provides the wrong format description for the actual video 359 // data 360 if (xf->format == XRT_FORMAT_YUYV422) { 361 struct xrt_rect roi = {.offset = {0, 0}, .extent = {.w = xf->width, .h = xf->height}}; 362 struct xrt_frame *xf_l8 = NULL; 363 364 u_frame_create_roi(xf, roi, &xf_l8); 365 xf_l8->width = 2 * xf->width; 366 xf_l8->format = XRT_FORMAT_L8; 367 368 xf = xf_l8; 369 release_xf = true; 370 } 371 372 // Dump mid-row of the 8 pix data line 373 union rift_s_frame_data row_data; 374 375 if (!parse_frame_data(xf, &row_data)) { 376 RIFT_S_TRACE("Invalid frame top-row data. Skipping"); 377 return; 378 } 379 380 RIFT_S_DEBUG("frame ctr %u ts %" PRIu64 381 " µS pair ctr %u " 382 "exposure[0] %u gain[0] %u unk %u", 383 (uint16_t)__le16_to_cpu(row_data.data.frame_ctr), (uint64_t)__le64_to_cpu(row_data.data.frame_ts), 384 (uint32_t)__le32_to_cpu(row_data.data.frame_ctr2), 385 (uint16_t)__le16_to_cpu(row_data.data.slam_exposure[0]), row_data.data.slam_gain[0], 386 (uint16_t)__le16_to_cpu(row_data.data.unknown1)); 387 388 // rift_s_hexdump_buffer("Row data", row_data.raw, sizeof(row_data.row)); 389 390 // If the top left pixel is > 128, send as SLAM frame else controller 391 if (row_data.data.frame_type & 0x80) { 392 int y_offset = get_y_offset(cam, 0, &row_data); 393 struct xrt_rect roi = {.offset = {0, y_offset}, .extent = {.w = xf->width, .h = 480}}; 394 395 struct xrt_frame *xf_crop = NULL; 396 u_frame_create_roi(xf, roi, &xf_crop); 397 u_sink_debug_push_frame(&cam->debug_sinks[0], xf_crop); 398 xrt_frame_reference(&xf_crop, NULL); 399 400 /* Extract camera frames and push to the tracker */ 401 struct xrt_frame *frames[RIFT_S_CAMERA_COUNT] = {0}; 402 for (int i = 0; i < RIFT_S_CAMERA_COUNT; i++) { 403 frames[i] = rift_s_camera_extract_frame(cam, CAM_IDX_TO_ID[i], xf, &row_data); 404 } 405 406 /* Update the exposure for all cameras based on the auto exposure for the left camera view */ 407 //! @todo Update expgain independently for each camera like in WMR 408 update_expgain(cam, frames[0]); 409 410 uint64_t frame_ts_ns = (uint64_t)__le64_to_cpu(row_data.data.frame_ts) * OS_NS_PER_USEC; 411 rift_s_tracker_push_slam_frames(cam->tracker, frame_ts_ns, frames); 412 413 for (int i = 0; i < RIFT_S_CAMERA_COUNT; i++) { 414 xrt_frame_reference(&frames[i], NULL); 415 } 416 } else { 417 struct xrt_rect roi = {.offset = {0, 40}, .extent = {.w = xf->width, .h = 480}}; 418 struct xrt_frame *xf_crop = NULL; 419 420 u_frame_create_roi(xf, roi, &xf_crop); 421 u_sink_debug_push_frame(&cam->debug_sinks[1], xf_crop); 422 xrt_frame_reference(&xf_crop, NULL); 423 } 424 if (release_xf) 425 xrt_frame_reference(&xf, NULL); 426} 427 428static void 429update_expgain(struct rift_s_camera *cam, struct xrt_frame *xf) 430{ 431 if (!cam->manual_control && xf != NULL) { 432 u_autoexpgain_update(cam->aeg, xf); 433 434 uint16_t new_target_exposure; 435 uint8_t new_target_gain; 436 437 new_target_exposure = 438 CLAMP(u_autoexpgain_get_exposure(cam->aeg), RIFT_S_MIN_EXPOSURE, RIFT_S_MAX_EXPOSURE); 439 new_target_gain = CLAMP(u_autoexpgain_get_gain(cam->aeg), RIFT_S_MIN_GAIN, RIFT_S_MAX_GAIN); 440 441 if (cam->target_exposure != new_target_exposure || cam->target_gain != new_target_gain) { 442 RIFT_S_DEBUG("AEG exposure now %u (cur %u) gain %u (cur %u)", new_target_exposure, 443 cam->target_exposure, new_target_gain, cam->target_gain); 444 445 os_mutex_lock(&cam->lock); 446 cam->target_exposure = new_target_exposure; 447 cam->target_gain = new_target_gain; 448 os_mutex_unlock(&cam->lock); 449 } 450 } 451} 452 453/* Called from the Rift S system device USB loop, so we can check 454 * and send an exposure/gain change command if needed */ 455void 456rift_s_camera_update(struct rift_s_camera *cam, struct os_hid_device *hid) 457{ 458 bool need_update = false; 459 int i; 460 461 os_mutex_lock(&cam->lock); 462 if (cam->target_exposure != cam->last_slam_exposure) { 463 for (i = 0; i < 5; i++) { 464 cam->camera_report.slam_frame_exposures[i] = cam->target_exposure; 465 } 466 cam->last_slam_exposure = cam->target_exposure; 467 need_update = true; 468 } 469 470 if (cam->target_gain != cam->last_slam_gain) { 471 for (i = 0; i < 5; i++) { 472 cam->camera_report.slam_frame_gains[i] = cam->target_gain; 473 } 474 cam->last_slam_gain = cam->target_gain; 475 need_update = true; 476 } 477 os_mutex_unlock(&cam->lock); 478 479 if (need_update) { 480 RIFT_S_DEBUG("Updating AEG exposure to %u gain %u", cam->target_exposure, cam->target_gain); 481 if (rift_s_protocol_send_camera_report(hid, &cam->camera_report) < 0) { 482 RIFT_S_WARN("Failed to update camera settings"); 483 } 484 } 485}