new plugin: Android hardware sensor source
authorMartin Kelly <martin@surround.io>
Tue, 12 Jul 2016 21:51:47 +0000 (14:51 -0700)
committerOlivier Crête <olivier.crete@collabora.com>
Thu, 14 Jul 2016 21:13:30 +0000 (17:13 -0400)
ahssrc is a new plugin that enables Gstreamer to read from the
android.hardware.Sensor Android sensors. These sensors are treated as
buffers and can be passed through and manipulated by the pipeline.

https://bugzilla.gnome.org/show_bug.cgi?id=768110

sys/androidmedia/Makefile.am
sys/androidmedia/gst-android-hardware-sensor.c [new file with mode: 0644]
sys/androidmedia/gst-android-hardware-sensor.h [new file with mode: 0644]
sys/androidmedia/gstahssrc.c [new file with mode: 0644]
sys/androidmedia/gstahssrc.h [new file with mode: 0644]
sys/androidmedia/gstamc.c
sys/androidmedia/gstsensors.h [new file with mode: 0644]
sys/androidmedia/org/freedesktop/gstreamer/androidmedia/GstAhsCallback.java [new file with mode: 0644]

index 3bb4ea36e36ac20d7f84c6d17ff2013990665ecf..c56eee2155ea6a6ad353dff769e5c21ee54df80b 100644 (file)
@@ -2,6 +2,7 @@ plugin_LTLIBRARIES = libgstandroidmedia.la
 
 libgstandroidmedia_la_SOURCES = \
        gstahcsrc.c \
+       gstahssrc.c \
        gstamcaudiodec.c \
        gstamc.c \
        gstamcsurface.c \
@@ -10,10 +11,12 @@ libgstandroidmedia_la_SOURCES = \
        gstamcvideoenc.c \
        gst-android-graphics-imageformat.c \
        gst-android-hardware-camera.c \
+       gst-android-hardware-sensor.c \
        gstjniutils.c
 
 noinst_HEADERS = \
        gstahcsrc.h \
+       gstahssrc.h \
        gstamcaudiodec.h \
        gstamc-constants.h \
        gstamc.h \
@@ -23,6 +26,7 @@ noinst_HEADERS = \
        gstamcvideoenc.h \
        gst-android-graphics-imageformat.h \
        gst-android-hardware-camera.h \
+       gst-android-hardware-sensor.h \
        gstjniutils.h
 
 libgstandroidmedia_la_CFLAGS = \
@@ -50,4 +54,5 @@ libgstandroidmedia_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
 androidmedia_java_classesdir = $(datadir)/gst-android/ndk-build/androidmedia/
 androidmedia_java_classes_DATA = \
        org/freedesktop/gstreamer/androidmedia/GstAhcCallback.java \
+       org/freedesktop/gstreamer/androidmedia/GstAhsCallback.java \
        org/freedesktop/gstreamer/androidmedia/GstAmcOnFrameAvailableListener.java
diff --git a/sys/androidmedia/gst-android-hardware-sensor.c b/sys/androidmedia/gst-android-hardware-sensor.c
new file mode 100644 (file)
index 0000000..f50dd83
--- /dev/null
@@ -0,0 +1,808 @@
+/*
+ * Copyright (C) 2016 SurroundIO
+ *   Author: Martin Kelly <martin@surround.io>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * The UNION_CAST macro is copyright:
+ * Copyright (C) 2008-2016 Matt Gallagher ( http://cocoawithlove.com ).
+ * All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for any purpose
+ * with or without fee is hereby granted, provided that the above copyright notice
+ * and this permission notice appear in all copies.
+
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <gmodule.h>
+
+#include "gstjniutils.h"
+#include "gst-android-hardware-sensor.h"
+
+static jobject (*gst_android_get_application_context) (void) = NULL;
+
+GST_DEBUG_CATEGORY_STATIC (ahs_debug);
+#define GST_CAT_DEFAULT ahs_debug
+
+/*
+ * See:
+ * http://www.cocoawithlove.com/2008/04/using-pointers-to-recast-in-c-is-bad.html
+ * for details.
+ */
+#define UNION_CAST(x, destType) \
+       (((union {__typeof__(x) a; destType b;})x).b)
+
+static struct
+{
+  jclass klass;
+  jstring SENSOR_SERVICE;
+  jmethodID getSystemService;
+} android_content_context = {
+0};
+
+static struct
+{
+  jclass klass;
+  jfieldID accuracy;
+  jfieldID values;
+} android_hardware_sensor_event = {
+0};
+
+static struct
+{
+  jclass klass;
+  jmethodID getDefaultSensor;;
+  jmethodID registerListener;
+  jmethodID unregisterListener;
+} android_hardware_sensor_manager = {
+0};
+
+static struct
+{
+  jclass klass;
+  jmethodID constructor;
+} org_freedesktop_gstreamer_androidmedia_gstahscallback = {
+0};
+
+GHashTable *sensor_sizes = NULL;
+static void
+gst_ah_sensor_sensor_sizes_init (void)
+{
+  gint i;
+  static struct
+  {
+    gint type;
+    gsize size;
+  } types[] = {
+    {AHS_SENSOR_TYPE_ACCELEROMETER, sizeof (GstAHSAccelerometerValues)},
+    {AHS_SENSOR_TYPE_AMBIENT_TEMPERATURE, sizeof (GstAHSAmbientTemperatureValues)},
+    {AHS_SENSOR_TYPE_GAME_ROTATION_VECTOR, sizeof (GstAHSGameRotationVectorValues)},
+    {AHS_SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR, sizeof (GstAHSGeomagneticRotationVectorValues)},
+    {AHS_SENSOR_TYPE_GRAVITY, sizeof (GstAHSGravityValues)},
+    {AHS_SENSOR_TYPE_GYROSCOPE, sizeof (GstAHSGyroscopeValues)},
+    {AHS_SENSOR_TYPE_GYROSCOPE_UNCALIBRATED, sizeof (GstAHSGyroscopeUncalibratedValues)},
+    {AHS_SENSOR_TYPE_HEART_RATE, sizeof (GstAHSHeartRateValues)},
+    {AHS_SENSOR_TYPE_LIGHT, sizeof (GstAHSLightValues)},
+    {AHS_SENSOR_TYPE_LINEAR_ACCELERATION, sizeof (GstAHSLinearAccelerationValues)},
+    {AHS_SENSOR_TYPE_MAGNETIC_FIELD, sizeof (GstAHSMagneticFieldValues)},
+    {AHS_SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED, sizeof (GstAHSMagneticFieldUncalibratedValues)},
+    {AHS_SENSOR_TYPE_ORIENTATION, sizeof (GstAHSOrientationValues)},
+    {AHS_SENSOR_TYPE_PRESSURE, sizeof (GstAHSPressureValues)},
+    {AHS_SENSOR_TYPE_PROXIMITY, sizeof (GstAHSProximityValues)},
+    {AHS_SENSOR_TYPE_RELATIVE_HUMIDITY, sizeof (GstAHSRelativeHumidityValues)},
+    {AHS_SENSOR_TYPE_ROTATION_VECTOR, sizeof (GstAHSRotationVectorValues)},
+    {AHS_SENSOR_TYPE_STEP_COUNTER, sizeof (GstAHSStepCounterValues)},
+    {AHS_SENSOR_TYPE_STEP_DETECTOR, sizeof (GstAHSStepDetectorValues)},
+  };
+
+  g_assert_null (sensor_sizes);
+
+  sensor_sizes = g_hash_table_new (g_int_hash, g_int_equal);
+  for (i = 0; i < G_N_ELEMENTS (types); i++)
+    g_hash_table_insert (sensor_sizes, &types[i].type, &types[i].size);
+}
+
+static void
+gst_ah_sensor_sensor_sizes_deinit (void)
+{
+  g_assert_nonnull (sensor_sizes);
+
+  g_hash_table_unref (sensor_sizes);
+  sensor_sizes = NULL;
+}
+
+gsize
+gst_ah_sensor_get_sensor_data_size (gint sensor_type)
+{
+  return *((gsize *) g_hash_table_lookup (sensor_sizes, &sensor_type));
+}
+
+static void
+gst_ah_sensor_on_sensor_changed (JNIEnv * env, jclass klass,
+    jobject sensor_event, jlong callback, jlong user_data)
+{
+  GstAHSensorCallback cb = (GstAHSensorCallback) (gsize) callback;
+
+  if (cb)
+    cb (sensor_event, (gpointer) (gsize) user_data);
+}
+
+static void
+gst_ah_sensor_on_accuracy_changed (JNIEnv * env, jclass klass,
+    jobject sensor, jint accuracy, jlong callback, jlong user_data)
+{
+  GstAHSAccuracyCallback cb = (GstAHSAccuracyCallback) (gsize) callback;
+
+  if (cb)
+    cb (sensor, accuracy, (gpointer) (gsize) user_data);
+}
+
+static gboolean natives_registered = FALSE;
+
+static JNINativeMethod native_methods[] = {
+  {(gchar *) "gst_ah_sensor_on_sensor_changed",
+        (gchar *) "(Landroid/hardware/SensorEvent;JJ)V",
+      (void *) gst_ah_sensor_on_sensor_changed},
+  {(gchar *) "gst_ah_sensor_on_accuracy_changed",
+        (gchar *) "(Landroid/hardware/Sensor;IJJ)V",
+      (void *) gst_ah_sensor_on_accuracy_changed}
+};
+
+static gboolean
+_init_classes (void)
+{
+  gint32 delay;
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GError *err = NULL;
+  jclass klass;
+  jfieldID fieldID;
+  GModule *module;
+  gboolean success;
+  gint32 type;
+
+  /*
+   * Lookup the Android function to get an Android context. This function will
+   * be provided when the plugin is built via ndk-build.
+   */
+  module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
+  if (!module)
+    goto failed;
+  success = g_module_symbol (module, "gst_android_get_application_context",
+      (gpointer *) & gst_android_get_application_context);
+  if (!success || !gst_android_get_application_context)
+    goto failed;
+  g_module_close (module);
+
+  /* android.content.Context */
+  klass = android_content_context.klass = gst_amc_jni_get_class (env, &err,
+      "android/content/Context");
+  if (!klass)
+    goto failed;
+  android_content_context.getSystemService =
+      gst_amc_jni_get_method_id (env, &err, klass, "getSystemService",
+      "(Ljava/lang/String;)Ljava/lang/Object;");
+  if (!android_content_context.getSystemService)
+    goto failed;
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "SENSOR_SERVICE",
+      "Ljava/lang/String;");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_object_field (env, &err, klass, fieldID,
+          &android_content_context.SENSOR_SERVICE))
+    goto failed;
+  android_content_context.SENSOR_SERVICE =
+      gst_amc_jni_object_make_global (env,
+      android_content_context.SENSOR_SERVICE);
+  if (!android_content_context.SENSOR_SERVICE)
+    goto failed;
+
+  /* android.hardware.SensorEvent */
+  klass = android_hardware_sensor_event.klass =
+      gst_amc_jni_get_class (env, &err, "android/hardware/SensorEvent");
+  if (!klass)
+    goto failed;
+  android_hardware_sensor_event.accuracy =
+      gst_amc_jni_get_field_id (env, &err, klass, "accuracy", "I");
+  if (!android_hardware_sensor_event.accuracy)
+    goto failed;
+  android_hardware_sensor_event.values =
+      gst_amc_jni_get_field_id (env, &err, klass, "values", "[F");
+  if (!android_hardware_sensor_event.values)
+    goto failed;
+
+  /* android.hardware.SensorManager */
+  klass = android_hardware_sensor_manager.klass =
+      gst_amc_jni_get_class (env, &err, "android/hardware/SensorManager");
+  if (!klass)
+    goto failed;
+  android_hardware_sensor_manager.getDefaultSensor =
+      gst_amc_jni_get_method_id (env, &err, klass,
+      "getDefaultSensor", "(I)Landroid/hardware/Sensor;");
+  if (!android_hardware_sensor_manager.getDefaultSensor)
+    goto failed;
+  android_hardware_sensor_manager.registerListener =
+      gst_amc_jni_get_method_id (env, &err, klass,
+      "registerListener",
+      "(Landroid/hardware/SensorEventListener;Landroid/hardware/Sensor;I)Z");
+  if (!android_hardware_sensor_manager.registerListener)
+    goto failed;
+  android_hardware_sensor_manager.unregisterListener =
+      gst_amc_jni_get_method_id (env, &err, klass,
+      "unregisterListener", "(Landroid/hardware/SensorEventListener;)V");
+  if (!android_hardware_sensor_manager.unregisterListener)
+    goto failed;
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "SENSOR_DELAY_FASTEST",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &delay))
+    goto failed;
+  if (delay != AHS_SENSOR_DELAY_FASTEST) {
+    GST_ERROR ("SENSOR_DELAY_FASTEST has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "SENSOR_DELAY_GAME",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &delay))
+    goto failed;
+  if (delay != AHS_SENSOR_DELAY_GAME) {
+    GST_ERROR ("SENSOR_DELAY_GAME has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "SENSOR_DELAY_NORMAL",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &delay))
+    goto failed;
+  if (delay != AHS_SENSOR_DELAY_NORMAL) {
+    GST_ERROR ("SENSOR_DELAY_NORMAL has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "SENSOR_DELAY_UI",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &delay))
+    goto failed;
+  if (delay != AHS_SENSOR_DELAY_UI) {
+    GST_ERROR ("SENSOR_DELAY_UI has changed value");
+    goto failed;
+  }
+
+  /* android.hardware.Sensor */
+  klass = gst_amc_jni_get_class (env, &err, "android/hardware/Sensor");
+  if (!klass)
+    goto failed;
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_ACCELEROMETER",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_ACCELEROMETER) {
+    GST_ERROR ("TYPE_ACCELEROMETER has changed value");
+    goto failed;
+  }
+
+  fieldID = gst_amc_jni_get_static_field_id (env, &err, klass,
+      "TYPE_AMBIENT_TEMPERATURE", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_AMBIENT_TEMPERATURE) {
+    GST_ERROR ("TYPE_AMBIENT_TEMPERATURE has changed value");
+    goto failed;
+  }
+
+  fieldID = gst_amc_jni_get_static_field_id (env, &err, klass,
+      "TYPE_GAME_ROTATION_VECTOR", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_GAME_ROTATION_VECTOR) {
+    GST_ERROR ("TYPE_GAME_ROTATION_VECTOR has changed value");
+    goto failed;
+  }
+
+  fieldID = gst_amc_jni_get_static_field_id (env, &err, klass,
+      "TYPE_GEOMAGNETIC_ROTATION_VECTOR", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR) {
+    GST_ERROR ("TYPE_GEOMAGNETIC_ROTATION_VECTOR has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_GRAVITY", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_GRAVITY) {
+    GST_ERROR ("TYPE_GRAVITY has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_GYROSCOPE", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_GYROSCOPE) {
+    GST_ERROR ("TYPE_GYROSCOPE has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass,
+      "TYPE_GYROSCOPE_UNCALIBRATED", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) {
+    GST_ERROR ("TYPE_GYROSCOPE_UNCALIBRATED has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_HEART_RATE",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_HEART_RATE) {
+    GST_ERROR ("TYPE_HEART_RATE has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_LIGHT", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_LIGHT) {
+    GST_ERROR ("TYPE_LIGHT has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass,
+      "TYPE_LINEAR_ACCELERATION", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_LINEAR_ACCELERATION) {
+    GST_ERROR ("TYPE_LINEAR_ACCELERATION has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_MAGNETIC_FIELD",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_MAGNETIC_FIELD) {
+    GST_ERROR ("TYPE_MAGNETIC_FIELD has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass,
+      "TYPE_MAGNETIC_FIELD_UNCALIBRATED", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED) {
+    GST_ERROR ("TYPE_MAGNETIC_FIELD_UNCALIBRATED has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_ORIENTATION",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_ORIENTATION) {
+    GST_ERROR ("TYPE_ORIENTATION has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_PRESSURE", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_PRESSURE) {
+    GST_ERROR ("TYPE_PRESSURE has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_PROXIMITY", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_PROXIMITY) {
+    GST_ERROR ("TYPE_PROXIMITY has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass,
+      "TYPE_RELATIVE_HUMIDITY", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_RELATIVE_HUMIDITY) {
+    GST_ERROR ("TYPE_RELATIVE_HUMIDITY has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_ROTATION_VECTOR",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_ROTATION_VECTOR) {
+    GST_ERROR ("TYPE_ROTATION_VECTOR has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass,
+      "TYPE_SIGNIFICANT_MOTION", "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_SIGNIFICANT_MOTION) {
+    GST_ERROR ("TYPE_SIGNIFICANT_MOTION has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_STEP_COUNTER",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_STEP_COUNTER) {
+    GST_ERROR ("TYPE_STEP_COUNTER has changed value");
+    goto failed;
+  }
+
+  fieldID =
+      gst_amc_jni_get_static_field_id (env, &err, klass, "TYPE_STEP_DETECTOR",
+      "I");
+  if (!fieldID)
+    goto failed;
+  if (!gst_amc_jni_get_static_int_field (env, &err, klass, fieldID, &type))
+    goto failed;
+  if (type != AHS_SENSOR_TYPE_STEP_DETECTOR) {
+    GST_ERROR ("TYPE_STEP_DETECTOR has changed value");
+    goto failed;
+  }
+
+  /* org.freedesktop.gstreamer.androidmedia.GstAhsCallback */
+  if (!org_freedesktop_gstreamer_androidmedia_gstahscallback.klass) {
+    org_freedesktop_gstreamer_androidmedia_gstahscallback.klass =
+        gst_amc_jni_get_class (env, &err,
+        "org/freedesktop/gstreamer/androidmedia/GstAhsCallback");
+  }
+  if (!org_freedesktop_gstreamer_androidmedia_gstahscallback.klass)
+    goto failed;
+  org_freedesktop_gstreamer_androidmedia_gstahscallback.constructor =
+      gst_amc_jni_get_method_id (env, &err,
+      org_freedesktop_gstreamer_androidmedia_gstahscallback.klass, "<init>",
+      "(JJJ)V");
+  if (!org_freedesktop_gstreamer_androidmedia_gstahscallback.constructor)
+    goto failed;
+
+  if ((*env)->RegisterNatives (env,
+          org_freedesktop_gstreamer_androidmedia_gstahscallback.klass,
+          native_methods, G_N_ELEMENTS (native_methods))) {
+    GST_ERROR ("Failed to register native methods for GstAhsCallback");
+    goto failed;
+  }
+  natives_registered = TRUE;
+
+  return TRUE;
+
+failed:
+  if (err) {
+    GST_ERROR ("Failed to initialize Android classes: %s", err->message);
+    g_clear_error (&err);
+  }
+
+  return FALSE;
+}
+
+gboolean
+gst_android_hardware_sensor_init (void)
+{
+  GST_DEBUG_CATEGORY_INIT (ahs_debug, "ahs", 0,
+      "Android Gstreamer Hardware Sensor");
+  if (!_init_classes ()) {
+    gst_android_hardware_sensor_deinit ();
+    return FALSE;
+  }
+
+  gst_ah_sensor_sensor_sizes_init ();
+
+  return TRUE;
+}
+
+void
+gst_android_hardware_sensor_deinit (void)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+
+  if (android_content_context.SENSOR_SERVICE) {
+    gst_amc_jni_object_unref (env, android_content_context.SENSOR_SERVICE);
+    android_content_context.SENSOR_SERVICE = NULL;
+  }
+
+  if (android_content_context.klass) {
+    gst_amc_jni_object_unref (env, android_content_context.klass);
+    android_content_context.klass = NULL;
+  }
+
+  if (android_hardware_sensor_event.klass) {
+    gst_amc_jni_object_unref (env, android_hardware_sensor_event.klass);
+    android_hardware_sensor_event.klass = NULL;
+  }
+
+  if (android_hardware_sensor_manager.klass) {
+    gst_amc_jni_object_unref (env, android_hardware_sensor_manager.klass);
+    android_hardware_sensor_manager.klass = NULL;
+  }
+
+  if (org_freedesktop_gstreamer_androidmedia_gstahscallback.klass) {
+    if (natives_registered) {
+      (*env)->UnregisterNatives (env,
+          org_freedesktop_gstreamer_androidmedia_gstahscallback.klass);
+      natives_registered = FALSE;
+    }
+    gst_amc_jni_object_unref (env,
+        org_freedesktop_gstreamer_androidmedia_gstahscallback.klass);
+    org_freedesktop_gstreamer_androidmedia_gstahscallback.klass = NULL;
+  }
+
+  gst_ah_sensor_sensor_sizes_deinit ();
+}
+
+GstAHSensorManager *
+gst_ah_sensor_get_manager (void)
+{
+  jobject context;
+  GError *err = NULL;
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GstAHSensorManager *manager;
+  jobject object;
+  gboolean success;
+
+  context = gst_android_get_application_context ();
+  success = gst_amc_jni_call_object_method (env, &err, context,
+      android_content_context.getSystemService,
+      &object, android_content_context.SENSOR_SERVICE);
+  if (!success)
+    return NULL;
+
+  object = gst_amc_jni_object_make_global (env, object);
+  if (!object)
+    return NULL;
+
+  manager = g_slice_new (GstAHSensorManager);
+  manager->object = object;
+
+  return manager;
+}
+
+GstAHSensor *
+gst_ah_sensor_get_default_sensor (GstAHSensorManager * self, gint32 sensor_type)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GError *err = NULL;
+  jobject object;
+  GstAHSensor *sensor;
+
+  if (!gst_amc_jni_call_object_method (env, &err, self->object,
+          android_hardware_sensor_manager.getDefaultSensor,
+          &object, sensor_type))
+    return NULL;
+
+  object = gst_amc_jni_object_make_global (env, object);
+  if (!object)
+    return NULL;
+
+  sensor = g_slice_new (GstAHSensor);
+  sensor->object = object;
+
+  return sensor;
+}
+
+GstAHSensorEventListener *
+gst_ah_sensor_create_listener (GstAHSensorCallback sensor_cb,
+    GstAHSAccuracyCallback accuracy_cb, gpointer user_data)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GError *err = NULL;
+  GstAHSensorEventListener *listener;
+  jobject object;
+
+  object = gst_amc_jni_new_object (env,
+      &err,
+      TRUE,
+      org_freedesktop_gstreamer_androidmedia_gstahscallback.klass,
+      org_freedesktop_gstreamer_androidmedia_gstahscallback.constructor,
+      UNION_CAST (sensor_cb, jlong),
+      UNION_CAST (accuracy_cb, jlong),
+      UNION_CAST (user_data, jlong));
+  if (err) {
+    GST_ERROR ("Failed to create listener callback class");
+    g_clear_error (&err);
+    return NULL;
+  }
+
+  listener = g_slice_new (GstAHSensorEventListener);
+  listener->object = object;
+
+  return listener;
+}
+
+gboolean
+gst_ah_sensor_register_listener (GstAHSensorManager * self,
+    GstAHSensorEventListener * listener, GstAHSensor * sensor, gint32 delay)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GError *err = NULL;
+  gboolean success;
+
+  gst_amc_jni_call_boolean_method (env, &err, self->object,
+      android_hardware_sensor_manager.registerListener, &success,
+      listener->object, sensor->object, (jint) delay);
+  if (err) {
+    GST_ERROR ("Failed to call android.hardware.SensorManager.registerListener: %s",
+        err->message);
+    g_clear_error (&err);
+    return FALSE;
+  }
+  listener->registered = TRUE;
+
+  return TRUE;
+}
+
+void
+gst_ah_sensor_unregister_listener (GstAHSensorManager * self,
+    GstAHSensorEventListener * listener)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GError *err = NULL;
+
+  gst_amc_jni_call_void_method (env, &err, self->object,
+      android_hardware_sensor_manager.unregisterListener, listener->object);
+  if (err) {
+    GST_ERROR ("Failed to call android.hardware.SensorManager.unregisterListener: %s",
+        err->message);
+    g_clear_error (&err);
+  }
+  listener->registered = FALSE;
+}
+
+gboolean
+gst_ah_sensor_populate_event (GstAHSensorEvent * event, jobject event_object,
+    gint size)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GError *err = NULL;
+  jfloatArray object_array;
+  jfloat *values;
+
+  gst_amc_jni_get_int_field (env, &err,
+      event_object, android_hardware_sensor_event.accuracy, &event->accuracy);
+  if (err) {
+    GST_ERROR ("Failed to get sensor accuracy field: %s", err->message);
+    goto error;
+  }
+
+  gst_amc_jni_get_object_field (env, &err, event_object,
+      android_hardware_sensor_event.values, &object_array);
+  if (err) {
+    GST_ERROR ("Failed to get sensor values field: %s", err->message);
+    goto error;
+  }
+
+  values = (*env)->GetFloatArrayElements (env, object_array, NULL);
+  if (!values) {
+    GST_ERROR ("Failed to get float array elements from object array");
+    gst_amc_jni_object_local_unref (env, object_array);
+    return FALSE;
+  }
+  /* We can't use gst_amc_jni_object_make_global here because we need to call
+   * ReleaseFloatArrayElements before doing a local unref in the failure case,
+   * but gst_amc_jni_object_make_global would unref before we could Release.
+   */
+  event->data.array = gst_amc_jni_object_ref (env, object_array);
+  if (!event->data.array) {
+    (*env)->ReleaseFloatArrayElements (env, object_array, values, JNI_ABORT);
+    gst_amc_jni_object_local_unref (env, object_array);
+    return FALSE;
+  }
+  event->data.values = values;
+  gst_amc_jni_object_local_unref (env, object_array);
+
+  return TRUE;
+
+error:
+  g_clear_error (&err);
+  return FALSE;
+}
+
+void
+gst_ah_sensor_free_sensor_data (GstAHSensorData * data)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+
+  (*env)->ReleaseFloatArrayElements (env, data->array, data->values, JNI_ABORT);
+  gst_amc_jni_object_unref (env, data->array);
+}
diff --git a/sys/androidmedia/gst-android-hardware-sensor.h b/sys/androidmedia/gst-android-hardware-sensor.h
new file mode 100644 (file)
index 0000000..2dc79dc
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 SurroundIO
+ *   Author: Martin Kelly <martin@surround.io>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_ANDROID_HARDWARE_SENSOR_H__
+#define __GST_ANDROID_HARDWARE_SENSOR_H__
+
+#include <gst/gst.h>
+#include <jni.h>
+
+#include "gstsensors.h"
+
+G_BEGIN_DECLS
+
+typedef struct GstAHSensor
+{
+  /* < private > */
+  jobject object;
+} GstAHSensor;
+
+typedef struct GstAHSensorData
+{
+  jfloatArray array;
+  gfloat *values;
+} GstAHSensorData;
+
+typedef struct GstAHSensorEvent
+{
+  /*
+   * Note that we don't use the Android event timestamp, as it's not reliable.
+   * See https://code.google.com/p/android/issues/detail?id=7981 for more
+   * details.
+   */
+  gint32 accuracy;
+  GstAHSensorData data;
+} GstAHSensorEvent;
+
+typedef struct GstAHSensorEventListener
+{
+  /* < private > */
+  jobject object;
+
+  gboolean registered;
+} GstAHSensorEventListener;
+
+typedef struct GstAHSensorManager
+{
+  /* < private > */
+  jobject object;
+} GstAHSensorManager;
+
+gint gst_android_sensor_type_from_string (const gchar * type_str);
+
+/* android.hardware.SensorListener onSensorChanged */
+typedef void (*GstAHSensorCallback) (jobject sensor_event, gpointer user_data);
+
+/* android.hardware.SensorListener onAccuracyChanged */
+typedef void (*GstAHSAccuracyCallback) (jobject sensor, gint32 accuracy,
+    gpointer user_data);
+
+gboolean gst_android_hardware_sensor_init (void);
+void gst_android_hardware_sensor_deinit (void);
+
+/*
+ * Example usage (excluding error checking):
+ *
+ * GstAHSensorManager *manager = gst_ah_sensor_get_manager ();
+ * GstAHSensor *sensor =
+ *     gst_ah_sensor_get_default_sensor (manager, * sensor_type);
+ * GstAHSensorEventListener *listener =
+ *     gst_ah_sensor_create_listener * (change_cb, accuracy_cb, self);
+ * gst_ah_sensor_register_listener (manager, listener, sensor,
+ *     SensorDelay_SENSOR_DELAY_NORMAL);
+ */
+GstAHSensorManager *gst_ah_sensor_get_manager (void);
+GstAHSensor *gst_ah_sensor_get_default_sensor (GstAHSensorManager * manager,
+    gint32 sensor_type);
+GstAHSensorEventListener *gst_ah_sensor_create_listener (GstAHSensorCallback
+    sensor_cb, GstAHSAccuracyCallback accuracy_cb, gpointer user_data);
+gboolean gst_ah_sensor_register_listener (GstAHSensorManager * self,
+    GstAHSensorEventListener * listener, GstAHSensor * sensor, gint32 delay);
+void gst_ah_sensor_unregister_listener (GstAHSensorManager * self,
+    GstAHSensorEventListener * listener);
+
+gboolean gst_ah_sensor_populate_event (GstAHSensorEvent * event,
+    jobject event_object, gint size);
+void gst_ah_sensor_free_sensor_data (GstAHSensorData * data);
+
+/*
+ * These constants come from the matching SENSOR_DELAY_* TYPE_* constants found
+ * in the Android documentation:
+ *
+ * SENSOR_DELAY_*:
+ * https://developer.android.com/reference/android/hardware/SensorManager.html
+ *
+ * TYPE_*:
+ * https://developer.android.com/reference/android/hardware/Sensor.html
+ *
+ * They are intended to be passed into the registerListener callback for
+ * listener registration. Note that, although these are hardcoded, we also do
+ * paranoid runtime checks during plugin init to verify that the API values
+ * haven't changed. This is unlikely but seems like a good precaution. When
+ * adding values, please keep the two lists in sync.
+ */
+enum
+{
+  AHS_SENSOR_DELAY_FASTEST = 0,
+  AHS_SENSOR_DELAY_GAME = 1,
+  AHS_SENSOR_DELAY_NORMAL = 3,
+  AHS_SENSOR_DELAY_UI = 2
+};
+
+enum
+{
+  AHS_SENSOR_TYPE_ACCELEROMETER = 0x1,
+  AHS_SENSOR_TYPE_AMBIENT_TEMPERATURE = 0xd,
+  AHS_SENSOR_TYPE_GAME_ROTATION_VECTOR = 0xf,
+  AHS_SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR = 0x14,
+  AHS_SENSOR_TYPE_GRAVITY = 0x9,
+  AHS_SENSOR_TYPE_GYROSCOPE = 0x4,
+  AHS_SENSOR_TYPE_GYROSCOPE_UNCALIBRATED = 0x10,
+  AHS_SENSOR_TYPE_HEART_RATE = 0x15,
+  AHS_SENSOR_TYPE_LIGHT = 0x5,
+  AHS_SENSOR_TYPE_LINEAR_ACCELERATION = 0xa,
+  AHS_SENSOR_TYPE_MAGNETIC_FIELD = 0x2,
+  AHS_SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED = 0xe,
+  AHS_SENSOR_TYPE_ORIENTATION = 0x3,
+  AHS_SENSOR_TYPE_PRESSURE = 0x6,
+  AHS_SENSOR_TYPE_PROXIMITY = 0x8,
+  AHS_SENSOR_TYPE_RELATIVE_HUMIDITY = 0xc,
+  AHS_SENSOR_TYPE_ROTATION_VECTOR = 0xb,
+  AHS_SENSOR_TYPE_SIGNIFICANT_MOTION = 0x11,
+  AHS_SENSOR_TYPE_STEP_COUNTER = 0x13,
+  AHS_SENSOR_TYPE_STEP_DETECTOR = 0x12
+};
+
+gsize gst_ah_sensor_get_sensor_data_size (gint sensor_type);
+
+G_END_DECLS
+#endif /* __GST_ANDROID_HARDWARE_SENSOR_H__ */
diff --git a/sys/androidmedia/gstahssrc.c b/sys/androidmedia/gstahssrc.c
new file mode 100644 (file)
index 0000000..bebb196
--- /dev/null
@@ -0,0 +1,684 @@
+/* GStreamer android.hardware.Sensor Source
+ * Copyright (C) 2016 SurroundIO
+ *   Author: Martin Kelly <martin@surround.io>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+/**
+ * SECTION:element-gstahssrc
+ *
+ * The ahssrc element reads data from Android device sensors
+ * (android.hardware.Sensor).
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v ahssrc ! fakesink
+ * ]|
+ * Push Android sensor data into a fakesink.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <gst/gst.h>
+#include <gst/gstclock.h>
+#include <gst/base/gstbasesrc.h>
+#include <gst/base/gstpushsrc.h>
+#include "gstjniutils.h"
+#include "gst-android-hardware-sensor.h"
+#include "gstahssrc.h"
+#include "gstsensors.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_ahs_src_debug);
+#define GST_CAT_DEFAULT gst_ahs_src_debug
+
+#define parent_class gst_ahs_src_parent_class
+
+/* GObject */
+static void gst_ahs_src_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_ahs_src_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+static void gst_ahs_src_dispose (GObject * object);
+
+/* GstBaseSrc */
+static gboolean gst_ahs_src_set_caps (GstBaseSrc * src, GstCaps * caps);
+static gboolean gst_ahs_src_start (GstBaseSrc * src);
+static gboolean gst_ahs_src_stop (GstBaseSrc * src);
+static gboolean gst_ahs_src_get_size (GstBaseSrc * src, guint64 * size);
+static gboolean gst_ahs_src_is_seekable (GstBaseSrc * src);
+static gboolean gst_ahs_src_unlock (GstBaseSrc * src);
+static gboolean gst_ahs_src_unlock_stop (GstBaseSrc * src);
+
+/* GstPushSrc */
+static GstFlowReturn gst_ahs_src_create (GstPushSrc * src, GstBuffer ** buf);
+
+/* GstAHSSrc */
+static void gst_ahs_src_on_sensor_changed (jobject sensor_event,
+    gpointer user_data);
+static void gst_ahs_src_on_accuracy_changed (jobject sensor, gint accuracy,
+    gpointer user_data);
+static gboolean gst_ahs_src_register_callback (GstAHSSrc * self);
+
+enum
+{
+  PROP_0,
+  PROP_SENSOR_DELAY,
+  PROP_ALPHA,
+  PROP_SAMPLE_INTERVAL,
+  PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+#define GST_AHS_SRC_CAPS_STR GST_SENSOR_CAPS_MAKE (GST_SENSOR_FORMATS_ALL)
+
+static GstStaticPadTemplate gst_ahs_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_AHS_SRC_CAPS_STR));
+
+
+G_DEFINE_TYPE_WITH_CODE (GstAHSSrc, gst_ahs_src, GST_TYPE_PUSH_SRC,
+    GST_DEBUG_CATEGORY_INIT (gst_ahs_src_debug, "ahssrc", 0,
+        "Android hardware sensors"));
+
+#define GST_TYPE_AHS_SENSOR_DELAY (gst_ahs_src_get_sensor_delay ())
+static GType
+gst_ahs_src_get_sensor_delay (void)
+{
+  static GType ahs_src_sensor_delay = 0;
+
+  if (!ahs_src_sensor_delay) {
+    static GEnumValue sensor_delay[5];
+    sensor_delay[0].value = AHS_SENSOR_DELAY_FASTEST;
+    sensor_delay[0].value_name = "fastest";
+    sensor_delay[0].value_nick = "fastest";
+    sensor_delay[1].value = AHS_SENSOR_DELAY_GAME;
+    sensor_delay[1].value_name = "game";
+    sensor_delay[1].value_nick = "game";
+    sensor_delay[2].value = AHS_SENSOR_DELAY_NORMAL;
+    sensor_delay[2].value_name = "normal";
+    sensor_delay[2].value_nick = "normal";
+    sensor_delay[3].value = AHS_SENSOR_DELAY_UI;
+    sensor_delay[3].value_name = "ui";
+    sensor_delay[3].value_nick = "ui";
+    sensor_delay[4].value = 0;
+    sensor_delay[4].value_name = NULL;
+    sensor_delay[4].value_nick = NULL;
+
+    ahs_src_sensor_delay =
+        g_enum_register_static ("GstAhsSrcSensorDelay", sensor_delay);
+  }
+
+  return ahs_src_sensor_delay;
+}
+
+#define GST_TYPE_AHS_SENSOR_TYPE (gst_ahs_src_get_sensor_type ())
+static GType
+gst_ahs_src_get_sensor_type (void)
+{
+  static GType ahs_src_sensor_type = 0;
+
+  if (!ahs_src_sensor_type) {
+    static const GEnumValue sensor_types[] = {
+      {AHS_SENSOR_TYPE_ACCELEROMETER, "accelerometer"},
+      {AHS_SENSOR_TYPE_AMBIENT_TEMPERATURE, "ambient-temperature"},
+      {AHS_SENSOR_TYPE_GAME_ROTATION_VECTOR, "game-rotation-vector"},
+      {AHS_SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR, "geomagnetic-rotation-vector"},
+      {AHS_SENSOR_TYPE_GRAVITY, "gravity"},
+      {AHS_SENSOR_TYPE_GYROSCOPE, "gyroscope"},
+      {AHS_SENSOR_TYPE_GYROSCOPE_UNCALIBRATED, "gyroscope-uncalibrated"},
+      {AHS_SENSOR_TYPE_HEART_RATE, "heart-rate"},
+      {AHS_SENSOR_TYPE_LIGHT, "light"},
+      {AHS_SENSOR_TYPE_LINEAR_ACCELERATION, "linear-acceleration"},
+      {AHS_SENSOR_TYPE_MAGNETIC_FIELD, "magnetic-field"},
+      {AHS_SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED, "magnetic-field-uncalibrated"},
+      {AHS_SENSOR_TYPE_ORIENTATION, "orientation"},
+      {AHS_SENSOR_TYPE_PRESSURE, "pressure"},
+      {AHS_SENSOR_TYPE_PROXIMITY, "proximity"},
+      {AHS_SENSOR_TYPE_RELATIVE_HUMIDITY, "relative-humidity"},
+      {AHS_SENSOR_TYPE_ROTATION_VECTOR, "rotation-vector"},
+      {AHS_SENSOR_TYPE_STEP_COUNTER, "step-counter"},
+      {AHS_SENSOR_TYPE_STEP_DETECTOR, "step-detector"},
+      {0, NULL, NULL}
+    };
+
+    ahs_src_sensor_type =
+        g_enum_register_static ("GstAhsSrcSensorType", sensor_types);
+  }
+
+  return ahs_src_sensor_type;
+}
+
+static void
+gst_ahs_src_class_init (GstAHSSrcClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstBaseSrcClass *base_src_class = GST_BASE_SRC_CLASS (klass);
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+  GstPushSrcClass *push_src_class = GST_PUSH_SRC_CLASS (klass);
+
+  gobject_class->set_property = gst_ahs_src_set_property;
+  gobject_class->get_property = gst_ahs_src_get_property;
+  gobject_class->dispose = gst_ahs_src_dispose;
+
+  base_src_class->set_caps = GST_DEBUG_FUNCPTR (gst_ahs_src_set_caps);
+  base_src_class->start = GST_DEBUG_FUNCPTR (gst_ahs_src_start);
+  base_src_class->stop = GST_DEBUG_FUNCPTR (gst_ahs_src_stop);
+  base_src_class->get_size = GST_DEBUG_FUNCPTR (gst_ahs_src_get_size);
+  base_src_class->is_seekable = GST_DEBUG_FUNCPTR (gst_ahs_src_is_seekable);
+  base_src_class->unlock = GST_DEBUG_FUNCPTR (gst_ahs_src_unlock);
+  base_src_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_ahs_src_unlock_stop);
+
+  push_src_class->create = GST_DEBUG_FUNCPTR (gst_ahs_src_create);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_ahs_src_template));
+
+  properties[PROP_SENSOR_DELAY] = g_param_spec_enum ("sensor-delay",
+      "Sensor delay", "Configure the sensor rate", GST_TYPE_AHS_SENSOR_DELAY,
+      AHS_SENSOR_DELAY_NORMAL,
+      G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (gobject_class, PROP_SENSOR_DELAY,
+      properties[PROP_SENSOR_DELAY]);
+
+  properties[PROP_ALPHA] = g_param_spec_double ("alpha", "Alpha",
+      "Alpha value used for exponential smoothing (between 0.0 and 1.0)", 0.0,
+      1.0, 0.2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (gobject_class, PROP_ALPHA,
+      properties[PROP_ALPHA]);
+
+  properties[PROP_SAMPLE_INTERVAL] = g_param_spec_uint ("sample-interval",
+      "Sample interval",
+      "Sample interval (for interval n, will output a smoothed average every "
+      "nth sample)", 1, G_MAXUINT, 1,
+      G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (gobject_class, PROP_SAMPLE_INTERVAL,
+      properties[PROP_SAMPLE_INTERVAL]);
+
+  gst_element_class_set_static_metadata (element_class,
+      "Android hardware sensors", "Source/Sensor/Device",
+      "Source for Android hardware sensor data",
+      "Martin Kelly <martin@surround.io>");
+}
+
+static gboolean
+_data_queue_check_full (GstDataQueue * queue, guint visible,
+    guint bytes, guint64 time, gpointer checkdata)
+{
+  return FALSE;
+}
+
+static void
+gst_ahs_src_init (GstAHSSrc * self)
+{
+  gst_base_src_set_live (GST_BASE_SRC (self), TRUE);
+  gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME);
+  gst_base_src_set_do_timestamp (GST_BASE_SRC (self), FALSE);
+
+  self->sensor_enum_class = g_type_class_ref (GST_TYPE_AHS_SENSOR_TYPE);
+  self->sensor_type_name = NULL;
+
+  self->manager = NULL;
+  self->sensor = NULL;
+  self->listener = NULL;
+  self->callback_registered = FALSE;
+
+  self->queue = gst_data_queue_new (_data_queue_check_full, NULL, NULL, NULL);
+
+  self->previous_time = GST_CLOCK_TIME_NONE;
+  self->sample_index = 0;
+  self->current_sample = NULL;
+}
+
+static void
+gst_ahs_src_dispose (GObject * object)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GstAHSSrc *self = GST_AHS_SRC (object);
+
+  if (self->manager) {
+    gst_amc_jni_object_unref (env, self->manager->object);
+    g_slice_free (GstAHSensorManager, self->manager);
+    self->manager = NULL;
+  }
+
+  if (self->sensor) {
+    gst_amc_jni_object_unref (env, self->sensor->object);
+    g_slice_free (GstAHSensor, self->sensor);
+    self->sensor = NULL;
+  }
+
+  if (self->listener) {
+    gst_amc_jni_object_unref (env, self->listener->object);
+    g_slice_free (GstAHSensorEventListener, self->listener);
+    self->listener = NULL;
+  }
+
+  if (self->current_sample) {
+    g_free (self->current_sample);
+    self->current_sample = NULL;
+  }
+
+  if (self->sensor_enum_class) {
+    g_type_class_unref (self->sensor_enum_class);
+    self->sensor_enum_class = NULL;
+  }
+
+  if (self->sensor_type_name) {
+    g_free ((gpointer) self->sensor_type_name);
+    self->sensor_type_name = NULL;
+  }
+
+  if (self->queue) {
+    g_object_unref (self->queue);
+    self->queue = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_ahs_src_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstAHSSrc *self = GST_AHS_SRC (object);
+
+  /*
+   * Take the mutex to protect against callbacks or changes to the properties
+   * that the callback uses (e.g. caps changes).
+   */
+  GST_OBJECT_LOCK (self);
+
+  switch (prop_id) {
+    case PROP_SENSOR_DELAY:
+      self->sensor_delay = g_value_get_enum (value);
+      /*
+       * If we already have a callback running, reregister with the new delay.
+       * Otherwise, wait for the pipeline to start before we register.
+       */
+      if (self->callback_registered)
+        gst_ahs_src_register_callback (self);
+      break;
+    case PROP_ALPHA:
+      self->alpha = g_value_get_double (value);
+      break;
+    case PROP_SAMPLE_INTERVAL:
+      self->sample_interval = g_value_get_uint (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+
+  GST_OBJECT_UNLOCK (self);
+}
+
+static void
+gst_ahs_src_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstAHSSrc *self = GST_AHS_SRC (object);
+
+  switch (prop_id) {
+    case PROP_SENSOR_DELAY:
+      g_value_set_enum (value, self->sensor_delay);
+    case PROP_ALPHA:
+      g_value_set_double (value, self->alpha);
+      break;
+    case PROP_SAMPLE_INTERVAL:
+      g_value_set_uint (value, self->sample_interval);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static gboolean
+gst_ahs_src_register_callback (GstAHSSrc * self)
+{
+  if (self->callback_registered) {
+    gst_ah_sensor_unregister_listener (self->manager, self->listener);
+    self->callback_registered = FALSE;
+  }
+  if (!gst_ah_sensor_register_listener (self->manager, self->listener,
+          self->sensor, self->sensor_delay)) {
+    return FALSE;
+  }
+  self->callback_registered = TRUE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_ahs_src_change_sensor_type (GstAHSSrc * self, const gchar * type_str,
+    gint type)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+
+  /* Replace sensor type. */
+  if (self->sensor_type_name)
+    g_free ((gpointer) self->sensor_type_name);
+  self->sensor_type_name = type_str;
+  self->sensor_type = type;
+
+  /* Adjust buffer and buffer size. */
+  self->buffer_size = gst_ah_sensor_get_sensor_data_size (self->sensor_type);
+  g_assert (self->buffer_size != 0);
+  self->sample_length = self->buffer_size / sizeof (*self->current_sample);
+  self->current_sample = g_realloc (self->current_sample, self->buffer_size);
+
+  /* Make sure we have a manager. */
+  if (!self->manager) {
+    self->manager = gst_ah_sensor_get_manager ();
+    if (!self->manager) {
+      GST_ERROR_OBJECT (self, "Failed to get sensor manager");
+      goto error_sensor_type_name;
+    }
+  }
+
+  /* Replace sensor object. */
+  if (self->sensor) {
+    gst_amc_jni_object_unref (env, self->sensor->object);
+    g_slice_free (GstAHSensor, self->sensor);
+  }
+  self->sensor = gst_ah_sensor_get_default_sensor (self->manager,
+      self->sensor_type);
+  if (!self->sensor) {
+    GST_ERROR_OBJECT (self, "Failed to get sensor type %s",
+        self->sensor_type_name);
+    goto error_manager;
+  }
+
+  /* Register for the callback, unregistering first if necessary. */
+  if (!gst_ahs_src_register_callback (self))
+    goto error_sensor;
+
+  return TRUE;
+
+error_sensor:
+  gst_amc_jni_object_unref (env, self->sensor->object);
+  g_slice_free (GstAHSensor, self->sensor);
+  self->sensor = NULL;
+error_manager:
+  gst_amc_jni_object_unref (env, self->manager->object);
+  g_slice_free (GstAHSensorManager, self->manager);
+  self->manager = NULL;
+error_sensor_type_name:
+  g_free ((gpointer) self->sensor_type_name);
+  self->sensor_type_name = NULL;
+  return FALSE;
+}
+
+static gboolean
+gst_ahs_src_set_caps (GstBaseSrc * src, GstCaps * caps)
+{
+  const GstStructure *caps_struct;
+  GstAHSSrc *self = GST_AHS_SRC (src);
+  gboolean success;
+  gint type;
+  const gchar *type_str;
+  GEnumValue *value;
+
+  caps_struct = gst_caps_get_structure (caps, 0);
+  type_str = gst_structure_get_string (caps_struct, "type");
+  value = g_enum_get_value_by_name (self->sensor_enum_class, type_str);
+  if (!value) {
+    GST_ERROR_OBJECT (self, "Failed to lookup sensor type %s", type_str);
+    return FALSE;
+  }
+  type_str = g_strdup (type_str);
+  type = value->value;
+
+  /*
+   * Take the mutex while changing the sensor type in case there are concurrent
+   * callbacks being processed.
+   */
+  GST_OBJECT_LOCK (self);
+  success = gst_ahs_src_change_sensor_type (self, type_str, type);
+  GST_OBJECT_UNLOCK (self);
+
+  if (!success)
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_ahs_src_start (GstBaseSrc * src)
+{
+  JNIEnv *env = gst_amc_jni_get_env ();
+  GstAHSSrc *self = GST_AHS_SRC (src);
+
+  g_assert_null (self->manager);
+  g_assert_null (self->listener);
+
+  self->manager = gst_ah_sensor_get_manager ();
+  if (!self->manager) {
+    GST_ERROR_OBJECT (self, "Failed to get sensor manager");
+    goto error;
+  }
+
+  self->previous_time = GST_CLOCK_TIME_NONE;
+
+  self->listener = gst_ah_sensor_create_listener (gst_ahs_src_on_sensor_changed,
+      gst_ahs_src_on_accuracy_changed, self);
+  if (!self->listener) {
+    GST_ERROR_OBJECT (self, "Failed to create sensor listener");
+    goto error_manager;
+  }
+
+  return TRUE;
+
+error_manager:
+  gst_amc_jni_object_unref (env, self->manager->object);
+  g_slice_free (GstAHSensorManager, self->manager);
+  self->manager = NULL;
+error:
+  return FALSE;
+}
+
+static gboolean
+gst_ahs_src_stop (GstBaseSrc * src)
+{
+  GstAHSSrc *self = GST_AHS_SRC (src);
+
+  g_assert_nonnull (self->manager);
+  g_assert_nonnull (self->sensor);
+  g_assert_nonnull (self->listener);
+
+  gst_ah_sensor_unregister_listener (self->manager, self->listener);
+  self->previous_time = GST_CLOCK_TIME_NONE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_ahs_src_get_size (GstBaseSrc * src, guint64 * size)
+{
+  GstAHSSrc *self = GST_AHS_SRC (src);
+
+  return self->buffer_size;
+}
+
+static gboolean
+gst_ahs_src_is_seekable (GstBaseSrc * src)
+{
+  return FALSE;
+}
+
+static gboolean
+gst_ahs_src_unlock (GstBaseSrc * src)
+{
+  GstAHSSrc *self = GST_AHS_SRC (src);
+
+  gst_data_queue_set_flushing (self->queue, TRUE);
+
+  return TRUE;
+}
+
+static gboolean
+gst_ahs_src_unlock_stop (GstBaseSrc * src)
+{
+  GstAHSSrc *self = GST_AHS_SRC (src);
+
+  gst_data_queue_set_flushing (self->queue, FALSE);
+
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_ahs_src_create (GstPushSrc * src, GstBuffer ** buffer)
+{
+  GstAHSSrc *self = GST_AHS_SRC (src);
+  GstDataQueueItem *item;
+
+  if (!gst_data_queue_pop (self->queue, &item)) {
+    GST_INFO_OBJECT (self, "data queue is empty");
+    return GST_FLOW_FLUSHING;
+  }
+
+  GST_DEBUG_OBJECT (self, "creating buffer %p->%p", item, item->object);
+
+  *buffer = GST_BUFFER (item->object);
+  g_slice_free (GstDataQueueItem, item);
+
+  return GST_FLOW_OK;
+}
+
+static void
+gst_ahs_src_free_data_queue_item (GstDataQueueItem * item)
+{
+  gst_buffer_unref (GST_BUFFER (item->object));
+  g_slice_free (GstDataQueueItem, item);
+}
+
+static void
+gst_ahs_src_update_smoothing (GstAHSSrc * self, const GstAHSensorEvent * event)
+{
+  gint i;
+
+  /*
+   * Since we're doing exponential smoothing, the first sample needs to be
+   * special-cased to prevent it from being artificially lowered by the alpha
+   * smoothing factor.
+   */
+  if (self->sample_index == 0) {
+    for (i = 0; i < self->sample_length; i++) {
+      self->current_sample[i] = event->data.values[i];
+    }
+  } else {
+    for (i = 0; i < self->sample_length; i++)
+      self->current_sample[i] =
+        (1-self->alpha) * self->current_sample[i] + self->alpha * event->data.values[i];
+  }
+}
+
+static void
+gst_ahs_src_on_sensor_changed (jobject event_object, gpointer user_data)
+{
+  GstBuffer *buffer;
+  GstClockTime buffer_time;
+  gfloat *data;
+  GstAHSensorEvent event;
+  GstDataQueueItem *item;
+  GstClock *pipeline_clock;
+  GstAHSSrc *self = GST_AHS_SRC (user_data);
+  gboolean success;
+
+  GST_OBJECT_LOCK (self);
+
+  pipeline_clock = GST_ELEMENT_CLOCK (self);
+  /* If the clock is NULL, the pipeline is not yet set to PLAYING. */
+  if (pipeline_clock == NULL)
+    goto done;
+
+  /*
+   * Unfortunately, the timestamp reported in the Android SensorEvent timestamp
+   * is not guaranteed to use any particular clock. On some device models, it
+   * uses system time, and on other models, it uses monotonic time. In addition,
+   * in some cases, the units are microseconds, and in other cases they are
+   * nanoseconds. Thus we cannot slave it to the pipeline clock or use any
+   * similar strategy that would allow us to correlate the two clocks. So
+   * instead, we approximate the buffer timestamp using the pipeline clock.
+   *
+   * See here for more details on issues with the Android SensorEvent timestamp:
+   * https://code.google.com/p/android/issues/detail?id=7981
+   */
+  buffer_time =
+    gst_clock_get_time (pipeline_clock) - GST_ELEMENT_CAST (self)->base_time;
+
+  success =
+      gst_ah_sensor_populate_event (&event, event_object, self->buffer_size);
+  if (!success) {
+    GST_ERROR_OBJECT (self, "Failed to populate sensor event");
+    goto done;
+  }
+
+  gst_ahs_src_update_smoothing (self, &event);
+  gst_ah_sensor_free_sensor_data (&event.data);
+  self->sample_index++;
+  if (self->sample_index < self->sample_interval)
+    goto done;
+  self->sample_index = 0;
+
+  /*
+   * We want to send off this sample; copy it into a separate data struct so we
+   * can continue using current_sample for aggregating future samples.
+   */
+  data = g_malloc (self->buffer_size);
+  memcpy (data, self->current_sample, self->buffer_size);
+
+  /* Wrap the datapoint with a buffer and add it to the queue. */
+  buffer = gst_buffer_new_wrapped (data, self->buffer_size);
+  GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
+  GST_BUFFER_PTS (buffer) = buffer_time;
+
+  item = g_slice_new (GstDataQueueItem);
+  item->object = GST_MINI_OBJECT (buffer);
+  item->size = gst_buffer_get_size (buffer);
+  item->duration = GST_BUFFER_DURATION (buffer);
+  item->visible = TRUE;
+  item->destroy = (GDestroyNotify) gst_ahs_src_free_data_queue_item;
+
+  success = gst_data_queue_push (self->queue, item);
+  if (!success) {
+    GST_ERROR_OBJECT (self, "Could not add buffer to queue");
+    gst_ahs_src_free_data_queue_item (item);
+    goto done;
+  }
+
+done:
+  GST_OBJECT_UNLOCK (self);
+}
+
+static void
+gst_ahs_src_on_accuracy_changed (jobject sensor, gint accuracy,
+    gpointer user_data)
+{
+  GstAHSSrc *self = GST_AHS_SRC (user_data);
+
+  /* TODO: Perhaps we should do something more with this information. */
+  GST_DEBUG_OBJECT (self, "Accuracy changed on sensor %p and is now %d", sensor,
+      accuracy);
+}
diff --git a/sys/androidmedia/gstahssrc.h b/sys/androidmedia/gstahssrc.h
new file mode 100644 (file)
index 0000000..9c00ca7
--- /dev/null
@@ -0,0 +1,80 @@
+/* GStreamer android.hardware.Sensor Source
+ * Copyright (C) 2016 SurroundIO
+ *   Author: Martin Kelly <martin@surround.io>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _GST_AHSSRC_H__
+#define _GST_AHSSRC_H__
+
+#include <gst/base/gstdataqueue.h>
+#include <gst/base/gstpushsrc.h>
+
+#include "gst-android-hardware-sensor.h"
+
+G_BEGIN_DECLS
+#define GST_TYPE_AHS_SRC   (gst_ahs_src_get_type())
+#define GST_AHS_SRC(obj)   (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AHS_SRC,GstAHSSrc))
+#define GST_AHS_SRC_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AHS_SRC,GstAHSSrcClass))
+#define GST_IS_AHS_SRC(obj)   (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AHS_SRC))
+#define GST_IS_AHS_SRC_CLASS(obj)   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AHS_SRC))
+typedef struct _GstAHSSrc GstAHSSrc;
+typedef struct _GstAHSSrcClass GstAHSSrcClass;
+
+struct _GstAHSSrc
+{
+  /* < private > */
+  GstPushSrc parent;
+
+  /* properties */
+  gint32 sensor_delay;
+  gdouble alpha;
+  guint sample_interval;
+
+  /* sensor type information */
+  GEnumClass *sensor_enum_class;
+  gint sensor_type;
+  const gchar *sensor_type_name;
+
+  /* JNI wrapper classes */
+  GstAHSensorManager *manager;
+  GstAHSensor *sensor;
+  GstAHSensorEventListener *listener;
+
+  /* timestamping */
+  GstClockTime previous_time;
+  gfloat *current_sample;
+
+  /* buffers */
+  gboolean callback_registered;
+  gint sample_index;
+  gint sample_length;
+  gint buffer_size;
+
+  /* multiprocessing */
+  GstDataQueue *queue;
+};
+
+struct _GstAHSSrcClass
+{
+  GstPushSrcClass parent_class;
+};
+
+GType gst_ahs_src_get_type (void);
+
+G_END_DECLS
+#endif
index 78fd8691f272845d79c7fc374d347e49caa40bcb..9eb4a50bb9662597674eb8e425f75db79f860d91 100644 (file)
@@ -30,6 +30,7 @@
 #endif
 
 #include "gstahcsrc.h"
+#include "gstahssrc.h"
 
 #include "gstamc.h"
 #include "gstamc-constants.h"
@@ -3337,13 +3338,24 @@ plugin_init (GstPlugin * plugin)
     goto failed_graphics_imageformat;
   }
 
+  if (!gst_android_hardware_sensor_init ()) {
+    goto failed_hardware_camera;
+  }
+
   if (!gst_element_register (plugin, "ahcsrc", GST_RANK_NONE, GST_TYPE_AHC_SRC)) {
     GST_ERROR ("Failed to register android camera source");
-    goto failed_hardware_camera;
+    goto failed_hardware_sensor;
+  }
+
+  if (!gst_element_register (plugin, "ahssrc", GST_RANK_NONE, GST_TYPE_AHS_SRC)) {
+    GST_ERROR ("Failed to register android sensor source");
+    goto failed_hardware_sensor;
   }
 
   return TRUE;
 
+failed_hardware_sensor:
+  gst_android_hardware_sensor_deinit ();
 failed_hardware_camera:
   gst_android_hardware_camera_deinit ();
 failed_graphics_imageformat:
diff --git a/sys/androidmedia/gstsensors.h b/sys/androidmedia/gstsensors.h
new file mode 100644 (file)
index 0000000..8b7dc89
--- /dev/null
@@ -0,0 +1,182 @@
+/* Copyright (C) 2016 SurroundIO
+ *   Author: Martin Kelly <martin@surround.io>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _GST_AHSCAPS_H__
+#define _GST_AHSCAPS_H__
+
+G_BEGIN_DECLS
+
+#define GST_SENSOR_FORMATS_ALL "{" \
+     "accelerometer, " \
+     "ambient-temperature, " \
+     "game-rotation-vector, " \
+     "geomagnetic-rotation-vector, " \
+     "gravity, " \
+     "gyroscope, " \
+     "gyroscope-uncalibrated, " \
+     "heart-rate, " \
+     "light, " \
+     "linear-acceleration, " \
+     "magnetic-field, " \
+     "magnetic-field-uncalibrated, " \
+     "orientation, " \
+     "pressure, " \
+     "proximity, " \
+     "relative-humidity, " \
+     "rotation-vector, " \
+     "significant-motion, " \
+     "step-counter, " \
+     "step-detector" \
+   "}"
+
+#define GST_SENSOR_CAPS_MAKE(format)                             \
+    "application/sensor, " \
+    "type = (string) " format
+
+typedef struct GstAHSAccelerometerValues
+{
+  gfloat x;
+  gfloat y;
+  gfloat z;
+} GstAHSAccelerometerValues;
+
+typedef struct GstAHSAmbientTemperatureValues
+{
+  gfloat temperature;
+} GstAHSAmbientTemperatureValues;
+
+typedef struct GstAHSGameRotationVectorValues
+{
+  gfloat x;
+  gfloat y;
+  gfloat z;
+  gfloat cos;
+  gfloat accuracy;
+} GstAHSGameRotationVectorValues;
+
+typedef struct GstAHSGeomagneticRotationVectorValues
+{
+  gfloat x;
+  gfloat y;
+  gfloat z;
+  gfloat cos;
+  gfloat accuracy;
+} GstAHSGeomagneticRotationVectorValues;
+
+typedef struct GstAHSGravityValues
+{
+  gfloat x;
+  gfloat y;
+  gfloat z;
+} GstAHSGravityValues;
+
+typedef struct GstAHSGyroscopeValues
+{
+  gfloat x;
+  gfloat y;
+  gfloat z;
+} GstAHSGyroscopeValues;
+
+typedef struct GstAHSGyroscopeUncalibratedValues
+{
+  gfloat x_speed;
+  gfloat y_speed;
+  gfloat z_speed;
+  gfloat x_drift;
+  gfloat y_drift;
+  gfloat z_drift;
+} GstAHSGyroscopeUncalibratedValues;
+
+typedef struct GstAHSHeartRateValues
+{
+  gfloat bpm;
+} GstAHSHeartRateValues;
+
+typedef struct GstAHSLightValues
+{
+  gfloat lux;
+} GstAHSLightValues;
+
+typedef struct GstAHSLinearAccelerationValues
+{
+  gfloat x;
+  gfloat y;
+  gfloat z;
+} GstAHSLinearAccelerationValues;
+
+typedef struct GstAHSMagneticFieldValues
+{
+  gfloat x;
+  gfloat y;
+  gfloat z;
+} GstAHSMagneticFieldValues;
+
+typedef struct GstAHSMagneticFieldUncalibratedValues
+{
+  gfloat x_uncalib;
+  gfloat y_uncalib;
+  gfloat z_uncalib;
+  gfloat x_bias;
+  gfloat y_bias;
+  gfloat z_bias;
+} GstAHSMagneticFieldUncalibratedValues;
+
+typedef struct GstAHSOrientationValues
+{
+  gfloat azimuth;
+  gfloat pitch;
+  gfloat roll;
+} GstAHSOrientationValues;
+
+typedef struct GstAHSProximity
+{
+  gfloat distance;
+} GstAHSProximityValues;
+
+typedef struct GstAHSPressureValues
+{
+  gfloat pressure;
+} GstAHSPressureValues;
+
+typedef struct GstAHSRelativeHumidityValues
+{
+  gfloat humidity;
+} GstAHSRelativeHumidityValues;
+
+typedef struct GstAHSRotationVectorValues
+{
+  gfloat x;
+  gfloat y;
+  gfloat z;
+  gfloat cos;
+  gfloat accuracy;
+} GstAHSRotationVectorValues;
+
+typedef struct GstAHSStepCounterValues
+{
+  gfloat count;
+} GstAHSStepCounterValues;
+
+typedef struct GstAHSStepDetectorValues
+{
+  gfloat one;
+} GstAHSStepDetectorValues;
+
+G_END_DECLS
+#endif
diff --git a/sys/androidmedia/org/freedesktop/gstreamer/androidmedia/GstAhsCallback.java b/sys/androidmedia/org/freedesktop/gstreamer/androidmedia/GstAhsCallback.java
new file mode 100644 (file)
index 0000000..b6fb015
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 SurroundIO
+ *   Author: Martin Kelly <martin@surround.io>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer.androidmedia;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+
+public class GstAhsCallback implements SensorEventListener {
+    public long mUserData;
+    public long mSensorCallback;
+    public long mAccuracyCallback;
+
+    public static native void gst_ah_sensor_on_sensor_changed(SensorEvent event,
+                                                              long callback, long user_data);
+    public static native void gst_ah_sensor_on_accuracy_changed(Sensor sensor, int accuracy,
+                                                                long callback, long user_data);
+
+    public GstAhsCallback(long sensor_callback,
+        long accuracy_callback, long user_data) {
+        mSensorCallback = sensor_callback;
+        mAccuracyCallback = accuracy_callback;
+        mUserData = user_data;
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+      gst_ah_sensor_on_sensor_changed(event, mSensorCallback, mUserData);
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+      gst_ah_sensor_on_accuracy_changed(sensor, accuracy,
+          mAccuracyCallback, mUserData);
+    }
+}