diff --git a/CMakeLists.txt b/CMakeLists.txt index 35d3e830f..c5bcaea3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,9 +174,11 @@ cmake_dependent_option(XRT_BUILD_DRIVER_NS "Enable North Star driver" ON "XRT_HA # This one defaults to off, even if we find the deps. cmake_dependent_option(XRT_BUILD_DRIVER_SURVIVE "Enable libsurvive driver" OFF "SURVIVE_FOUND" OFF) +cmake_dependent_option(XRT_BUILD_DRIVER_ANDROID "Enable Android sensors driver" ON "ANDROID" OFF) + # You can set this from a superproject to add a driver # All drivers must be listed in here to be included in the generated header! -list(APPEND AVAILABLE_DRIVERS ARDUINO DUMMY HDK HYDRA NS OHMD PSMV PSVR RS V4L2 VIVE DAYDREAM REMOTE SURVIVE) +list(APPEND AVAILABLE_DRIVERS "ANDROID" ARDUINO DUMMY HDK HYDRA NS OHMD PSMV PSVR RS V4L2 VIVE DAYDREAM REMOTE SURVIVE) # Package name needs to be known by the native code itself. @@ -280,6 +282,7 @@ message(STATUS "# FEATURE_OPENXR_LAYER_CYLINDER: ${XRT_FEATURE_OPENXR_ message(STATUS "# FEATURE_OPENXR_LAYER_EQUIRECT: ${XRT_FEATURE_OPENXR_LAYER_EQUIRECT}") message(STATUS "# FEATURE_OPENXR_LAYER_EQUIRECT_LEGACY: ${XRT_FEATURE_OPENXR_LAYER_EQUIRECT_LEGACY}") message(STATUS "#") +message(STATUS "# DRIVER_ANDROID: ${XRT_BUILD_DRIVER_ANDROID}") message(STATUS "# DRIVER_ARDUINO: ${XRT_BUILD_DRIVER_ARDUINO}") message(STATUS "# DRIVER_DAYDREAM: ${XRT_BUILD_DRIVER_DAYDREAM}") message(STATUS "# DRIVER_DUMMY: ${XRT_BUILD_DRIVER_DUMMY}") diff --git a/doc/changes/drivers/mr.581.md b/doc/changes/drivers/mr.581.md new file mode 100644 index 000000000..f3bca74de --- /dev/null +++ b/doc/changes/drivers/mr.581.md @@ -0,0 +1 @@ +Add initial "Cardboard" phone-holder driver for Android. diff --git a/src/xrt/drivers/CMakeLists.txt b/src/xrt/drivers/CMakeLists.txt index f7520c8b0..5df409509 100644 --- a/src/xrt/drivers/CMakeLists.txt +++ b/src/xrt/drivers/CMakeLists.txt @@ -194,6 +194,19 @@ if (XRT_BUILD_DRIVER_SURVIVE) list(APPEND ENABLED_HEADSET_DRIVERS survive) endif() +if(XRT_BUILD_DRIVER_ANDROID) + set(ANDROID_SOURCE_FILES + android/android_prober.c + android/android_prober.h + android/android_sensors.c + android/android_sensors.h + ) + + add_library(drv_android STATIC ${ANDROID_SOURCE_FILES}) + target_link_libraries(drv_android PRIVATE xrt-interfaces aux_util aux_os ${ANDROID_LIBRARY}) + list(APPEND ENABLED_DRIVERS android) +endif() + if(ENABLED_HEADSET_DRIVERS) set(ENABLED_DRIVERS ${ENABLED_HEADSET_DRIVERS} ${ENABLED_DRIVERS}) list(SORT ENABLED_DRIVERS) diff --git a/src/xrt/drivers/android/android_prober.c b/src/xrt/drivers/android/android_prober.c new file mode 100644 index 000000000..9bd628c94 --- /dev/null +++ b/src/xrt/drivers/android/android_prober.c @@ -0,0 +1,91 @@ +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Interface to Android sensors prober code. + * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com> + * @ingroup drv_android + */ + +#include <stdio.h> +#include <stdlib.h> +#include <wchar.h> + +#include "xrt/xrt_prober.h" + +#include "util/u_misc.h" +#include "util/u_debug.h" +#include "util/u_logging.h" + +#include "android_prober.h" +#include "android_sensors.h" + + + +/* + * + * Defines & structs. + * + */ + +/*! + * Android prober struct. + * + * @ingroup drv_android + * @implements xrt_auto_prober + */ +struct android_prober +{ + struct xrt_auto_prober base; +}; + + +/* + * + * Static functions. + * + */ + +//! @private @memberof android_prober +static inline struct android_prober * +android_prober(struct xrt_auto_prober *p) +{ + return (struct android_prober *)p; +} + +//! @public @memberof android_prober +static void +android_prober_destroy(struct xrt_auto_prober *p) +{ + struct android_prober *pandroid = android_prober(p); + free(pandroid); +} + +//! @public @memberof android_prober +static struct xrt_device * +android_prober_autoprobe(struct xrt_auto_prober *xap, + cJSON *attached_data, + bool no_hmds, + struct xrt_prober *xp) +{ + struct android_device *dd = android_device_create(); + return &dd->base; +} + + +/* + * + * Exported functions. + * + */ + +struct xrt_auto_prober * +android_create_auto_prober() +{ + struct android_prober *p = U_TYPED_CALLOC(struct android_prober); + p->base.name = "Android"; + p->base.destroy = android_prober_destroy; + p->base.lelo_dallas_autoprobe = android_prober_autoprobe; + + return &p->base; +} diff --git a/src/xrt/drivers/android/android_prober.h b/src/xrt/drivers/android/android_prober.h new file mode 100644 index 000000000..5b8e60429 --- /dev/null +++ b/src/xrt/drivers/android/android_prober.h @@ -0,0 +1,41 @@ +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Interface to Android sensors driver. + * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com> + * @ingroup drv_android + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * @defgroup drv_android Android sensors driver + * @ingroup drv + * + * @brief Generic driver for phone sensors. + */ + +/*! + * Probing function for Android sensors. + * + * @ingroup drv_android + */ +struct xrt_auto_prober * +android_create_auto_prober(); + + +/*! + * @dir drivers/android + * + * @brief @ref drv_android files. + */ + + +#ifdef __cplusplus +} +#endif diff --git a/src/xrt/drivers/android/android_sensors.c b/src/xrt/drivers/android/android_sensors.c new file mode 100644 index 000000000..20be3ca74 --- /dev/null +++ b/src/xrt/drivers/android/android_sensors.c @@ -0,0 +1,260 @@ +// Copyright 2013, Fredrik Hultin. +// Copyright 2013, Jakob Bornecrantz. +// Copyright 2015, Joey Ferwerda. +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Android sensors driver code. + * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com> + * @ingroup drv_android + */ + +#include "android_sensors.h" + +#include "util/u_debug.h" +#include "util/u_device.h" +#include "util/u_var.h" + +// 60 events per second (in us). +#define POLL_RATE_USEC (1000L / 60) * 1000 + + +DEBUG_GET_ONCE_LOG_OPTION(android_log, "ANDROID_SENSORS_LOG", U_LOGGING_WARN) + +static inline struct android_device * +android_device(struct xrt_device *xdev) +{ + return (struct android_device *)xdev; +} + +// Callback for the Android sensor event queue +static int +android_sensor_callback(int fd, int events, void *data) +{ + struct android_device *d = (struct android_device *)data; + + if (d->accelerometer == NULL || d->gyroscope == NULL) + return 1; + + ASensorEvent event; + struct xrt_vec3 gyro; + struct xrt_vec3 accel; + while (ASensorEventQueue_getEvents(d->event_queue, &event, 1) > 0) { + + switch (event.type) { + case ASENSOR_TYPE_ACCELEROMETER: { + accel.x = event.acceleration.y; + accel.y = -event.acceleration.x; + accel.z = event.acceleration.z; + + ANDROID_TRACE(d, "accel %ld %.2f %.2f %.2f", + event.timestamp, accel.x, accel.y, + accel.z); + break; + } + case ASENSOR_TYPE_GYROSCOPE: { + gyro.x = -event.data[1]; + gyro.y = event.data[0]; + gyro.z = event.data[2]; + + ANDROID_TRACE(d, "gyro %ld %.2f %.2f %.2f", + event.timestamp, gyro.x, gyro.y, gyro.z); + + // TODO: Make filter handle accelerometer + struct xrt_vec3 null_accel; + + // Lock last and the fusion. + os_mutex_lock(&d->lock); + + m_imu_3dof_update(&d->fusion, event.timestamp, + &null_accel, &gyro); + + // Now done. + os_mutex_unlock(&d->lock); + } + default: + ANDROID_TRACE(d, "Unhandled event type %d", event.type); + } + } + + return 1; +} + +static void * +android_run_thread(void *ptr) +{ + struct android_device *d = (struct android_device *)ptr; + + d->sensor_manager = ASensorManager_getInstance(); + d->accelerometer = ASensorManager_getDefaultSensor( + d->sensor_manager, ASENSOR_TYPE_ACCELEROMETER); + d->gyroscope = ASensorManager_getDefaultSensor(d->sensor_manager, + ASENSOR_TYPE_GYROSCOPE); + + ALooper *looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); + + d->event_queue = ASensorManager_createEventQueue( + d->sensor_manager, looper, ALOOPER_POLL_CALLBACK, + android_sensor_callback, (void *)d); + + // Start sensors in case this was not done already. + if (d->accelerometer != NULL) { + ASensorEventQueue_enableSensor(d->event_queue, + d->accelerometer); + ASensorEventQueue_setEventRate(d->event_queue, d->accelerometer, + POLL_RATE_USEC); + } + if (d->gyroscope != NULL) { + ASensorEventQueue_enableSensor(d->event_queue, d->gyroscope); + ASensorEventQueue_setEventRate(d->event_queue, d->gyroscope, + POLL_RATE_USEC); + } + + int ret = 0; + while (ret != ALOOPER_POLL_ERROR) { + ret = ALooper_pollAll(0, NULL, NULL, NULL); + } + + return NULL; +} + + +/* + * + * Device functions. + * + */ + +static void +android_device_destroy(struct xrt_device *xdev) +{ + struct android_device *android = android_device(xdev); + + // Destroy the thread object. + os_thread_helper_destroy(&android->oth); + + // Now that the thread is not running we can destroy the lock. + os_mutex_destroy(&android->lock); + + // Destroy the fusion. + m_imu_3dof_close(&android->fusion); + + // Remove the variable tracking. + u_var_remove_root(android); + + free(android); +} + +static void +android_device_update_inputs(struct xrt_device *xdev) +{ + // Empty +} + +static void +android_device_get_tracked_pose(struct xrt_device *xdev, + enum xrt_input_name name, + uint64_t at_timestamp_ns, + struct xrt_space_relation *out_relation) +{ + (void)at_timestamp_ns; + + struct android_device *d = android_device(xdev); + out_relation->pose.orientation = d->fusion.rot; + + //! @todo assuming that orientation is actually currently tracked. + out_relation->relation_flags = (enum xrt_space_relation_flags)( + XRT_SPACE_RELATION_ORIENTATION_VALID_BIT | + XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT); +} + +static void +android_device_get_view_pose(struct xrt_device *xdev, + struct xrt_vec3 *eye_relation, + uint32_t view_index, + struct xrt_pose *out_pose) +{ + struct xrt_pose pose = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}}; + bool adjust = view_index == 0; + + pose.position.x = eye_relation->x / 2.0f; + pose.position.y = eye_relation->y / 2.0f; + pose.position.z = eye_relation->z / 2.0f; + + // Adjust for left/right while also making sure there aren't any -0.f. + if (pose.position.x > 0.0f && adjust) { + pose.position.x = -pose.position.x; + } + if (pose.position.y > 0.0f && adjust) { + pose.position.y = -pose.position.y; + } + if (pose.position.z > 0.0f && adjust) { + pose.position.z = -pose.position.z; + } + + *out_pose = pose; +} + +/* + * + * Prober functions. + * + */ + +struct android_device * +android_device_create() +{ + enum u_device_alloc_flags flags = (enum u_device_alloc_flags)( + U_DEVICE_ALLOC_HMD | U_DEVICE_ALLOC_TRACKING_NONE); + struct android_device *d = + U_DEVICE_ALLOCATE(struct android_device, flags, 1, 0); + + d->base.name = XRT_DEVICE_ANDROID; + d->base.destroy = android_device_destroy; + d->base.update_inputs = android_device_update_inputs; + d->base.get_tracked_pose = android_device_get_tracked_pose; + d->base.get_view_pose = android_device_get_view_pose; + d->base.inputs[0].name = XRT_INPUT_GENERIC_HEAD_POSE; + d->base.device_type = XRT_DEVICE_TYPE_HMD; + + d->ll = debug_get_log_option_android_log(); + + m_imu_3dof_init(&d->fusion, M_IMU_3DOF_USE_GRAVITY_DUR_20MS); + + // Everything done, finally start the thread. + int ret = os_thread_helper_start(&d->oth, android_run_thread, d); + if (ret != 0) { + ANDROID_ERROR(d, "Failed to start thread!"); + android_device_destroy(&d->base); + return NULL; + } + + // Setup info. + struct u_device_simple_info info; + info.display.w_pixels = 1280; + info.display.h_pixels = 720; + info.display.w_meters = 0.13f; + info.display.h_meters = 0.07f; + info.lens_horizontal_separation_meters = 0.13f / 2.0f; + info.lens_vertical_position_meters = 0.07f / 2.0f; + info.views[0].fov = 85.0f * (M_PI / 180.0f); + info.views[1].fov = 85.0f * (M_PI / 180.0f); + + if (!u_device_setup_split_side_by_side(&d->base, &info)) { + ANDROID_ERROR(d, "Failed to setup basic device info"); + android_device_destroy(&d->base); + return NULL; + } + + u_var_add_root(d, "Android phone", true); + u_var_add_ro_vec3_f32(d, &d->fusion.last.accel, "last.accel"); + u_var_add_ro_vec3_f32(d, &d->fusion.last.gyro, "last.gyro"); + + d->base.orientation_tracking_supported = true; + d->base.position_tracking_supported = false; + + ANDROID_DEBUG(d, "Created device!"); + + return d; +} diff --git a/src/xrt/drivers/android/android_sensors.h b/src/xrt/drivers/android/android_sensors.h new file mode 100644 index 000000000..cd46ff487 --- /dev/null +++ b/src/xrt/drivers/android/android_sensors.h @@ -0,0 +1,70 @@ +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Android sensors driver header. + * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com> + * @ingroup drv_android + */ + +#pragma once + +#include <android/sensor.h> + +#include "math/m_api.h" +#include "math/m_imu_pre.h" +#include "math/m_imu_3dof.h" + +#include "xrt/xrt_device.h" + +#include "os/os_threading.h" + +#include "util/u_logging.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * @implements xrt_device + */ +struct android_device +{ + struct xrt_device base; + struct os_thread_helper oth; + + ASensorManager *sensor_manager; + const ASensor *accelerometer; + const ASensor *gyroscope; + ASensorEventQueue *event_queue; + + struct + { + //! Lock for last and fusion. + struct os_mutex lock; + struct m_imu_3dof fusion; + }; + + enum u_logging_level ll; +}; + + +struct android_device * +android_device_create(); + + +/* + * + * Printing functions. + * + */ + +#define ANDROID_TRACE(d, ...) U_LOG_XDEV_IFL_T(&d->base, d->ll, __VA_ARGS__) +#define ANDROID_DEBUG(d, ...) U_LOG_XDEV_IFL_D(&d->base, d->ll, __VA_ARGS__) +#define ANDROID_INFO(d, ...) U_LOG_XDEV_IFL_I(&d->base, d->ll, __VA_ARGS__) +#define ANDROID_WARN(d, ...) U_LOG_XDEV_IFL_W(&d->base, d->ll, __VA_ARGS__) +#define ANDROID_ERROR(d, ...) U_LOG_XDEV_IFL_E(&d->base, d->ll, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif diff --git a/src/xrt/include/xrt/xrt_defines.h b/src/xrt/include/xrt/xrt_defines.h index 7c6653c1b..7b86f0f91 100644 --- a/src/xrt/include/xrt/xrt_defines.h +++ b/src/xrt/include/xrt/xrt_defines.h @@ -419,6 +419,7 @@ enum xrt_device_name XRT_DEVICE_VIVE_WAND, XRT_DEVICE_VIVE_TRACKER_GEN1, XRT_DEVICE_VIVE_TRACKER_GEN2, + XRT_DEVICE_ANDROID }; /*! diff --git a/src/xrt/targets/common/CMakeLists.txt b/src/xrt/targets/common/CMakeLists.txt index 662ab5738..db56c7905 100644 --- a/src/xrt/targets/common/CMakeLists.txt +++ b/src/xrt/targets/common/CMakeLists.txt @@ -76,6 +76,10 @@ if(XRT_BUILD_DRIVER_SURVIVE) target_link_libraries(target_lists PRIVATE drv_survive) endif() +if(XRT_BUILD_DRIVER_ANDROID) + target_link_libraries(target_lists PRIVATE drv_android) +endif() + #### # Instance # diff --git a/src/xrt/targets/common/target_lists.c b/src/xrt/targets/common/target_lists.c index 717f68ac3..56feda646 100644 --- a/src/xrt/targets/common/target_lists.c +++ b/src/xrt/targets/common/target_lists.c @@ -54,6 +54,10 @@ #include "daydream/daydream_interface.h" #endif +#ifdef XRT_BUILD_DRIVER_ANDROID +#include "android/android_prober.h" +#endif + /*! * Each entry should be a vendor ID (VID), product ID (PID), a "found" function, * and a string literal name. @@ -134,6 +138,10 @@ xrt_auto_prober_creator target_auto_list[] = { ns_create_auto_prober, #endif +#ifdef XRT_BUILD_DRIVER_ANDROID + android_create_auto_prober, +#endif + #ifdef XRT_BUILD_DRIVER_DUMMY // Dummy headset driver last. dummy_create_auto_prober,