1. Unit tests are added.
2. dummy_sensor.c/h offers dummy Tizen sensor framework for unit tests,
where we cannot activate Tizen sensor framework daemon.
3. Bugs were found with the unit tests and they are fixed.
- g_hash_table misuses
- property configuration errors
- frequency (framerate) handling
- Clean up and configured-status handling
- Mutex deadlock
- Timestamp handling
Changes v1->v2:
- Use G_USEC_PER_SEC for readability, suggested by dongju.chae@samsung.com
Changes v2->v3:
- Added error-clear in testcase, suggested by dongju.chae@samsung.com
- Removed unnecessary code (residue of internal tests), suggested by jy1210.jung@samsung.com
Changes v3->v4:
- Added free ops for pipelines, removed duplicated codes, suggested by jy1210.jung@samsung.com
Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
for (i = 0; i < elem->tensors_info.num_tensors; i++) {
size_t sz = ml_tensor_info_get_size (&elem->tensors_info.info[i]);
+ /* Not configured, yet. */
+ if (sz == 0)
+ ml_loge ("The caps for sink(%s) is not configured.", elem->name);
+
if (sz != data->tensors[i].size) {
ml_loge
("The sink event of [%s] cannot be handled because the tensor dimension mismatches.",
* Tizen-API / sensor_get_sensor_list(ACCELEROMETER, list, count);
* When the sequence is not specified, the first (.0) is chosen.
* You may specify the enum value of the sensor (sensor_type_e) defined
- * in sensor.h of Tizen with type, instead of typename.
+ * in sensor.h of Tizen with type.
*
* If sequence = -1 (default), we use "default sensor".
*
*
* @todo Add "Listener" mode (creates data only if there are updates)
*
+ * @todo Every mode should handle timestamp/duration properly!
+ *
* @todo Add "power management" options (Tizen sensor f/w accepts such)
*
* @todo Some sensor tpes are privileged. We need privilege control.
*/
#define DEFAULT_PROP_SEQUENCE -1
-#define _LOCK(obj) g_mutex_lock (&(obj)->lock);
-#define _UNLOCK(obj) g_mutex_unlock (&(obj)->lock);
+#define _LOCK(obj) g_mutex_lock (&(obj)->lock)
+#define _UNLOCK(obj) g_mutex_unlock (&(obj)->lock)
/**
* @brief Template for src pad.
"Produce verbose output", DEFAULT_PROP_SILENT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_TYPE,
- g_param_spec_enum ("typename", "Tizen Sensor Type Name (enum)",
+ g_param_spec_enum ("type", "Tizen Sensor Type (enum)",
"Tizen sensor type as a enum-name, defined in sensor.h of Tizen",
GST_TYPE_TIZEN_SENSOR_TYPE, SENSOR_ALL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
GST_TYPE_TIZEN_SENSOR_MODE, TZN_SENSOR_MODE_POLLING,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_FREQ,
- gst_param_spec_fraction ("freq", "Frequency",
+ gst_param_spec_fraction ("framerate", "Framerate",
"Rate of data retrievals from a sensor. Effective only when "
"mode is ACTIVE_POLLING",
0, 1, G_MAXINT, 1,
if (NULL == tizensensors) {
int i;
- tizensensors = g_hash_table_new (g_int_hash, g_int_equal);
+ tizensensors = g_hash_table_new (g_direct_hash, g_direct_equal);
for (i = 0; tizensensorspecs[i].type != SENSOR_LAST; i++) {
g_assert (g_hash_table_insert (tizensensors,
- (gpointer) tizensensorspecs[i].type,
+ GINT_TO_POINTER (tizensensorspecs[i].type),
&tizensensorspecs[i].tinfo) == TRUE);
g_assert (tizensensorspecs[i].value_count ==
tizensensorspecs[i].tinfo.dimension[0]);
static unsigned int
_ts_get_interval_ms (GstTensorSrcTIZENSENSOR * self)
{
+ if (self->freq_n == 0)
+ return 100; /* If it's 0Hz, assume 100ms interval */
+
g_assert (self->freq_d > 0 && self->freq_n > 0);
return gst_util_uint64_scale_int ((guint64) self->freq_d, 1000, self->freq_n);
{
int ret = 0;
const GstTensorInfo *val = g_hash_table_lookup (tizensensors,
- (gpointer) self->type);
+ GINT_TO_POINTER (self->type));
bool supported = false;
+ if (NULL == val)
+ g_printerr ("The given sensor type (%d) is not supported.\n", self->type);
g_assert (val);
self->src_spec = val;
GST_ERROR_OBJECT (self,
"The requested sensor sequence %d for sensor %d is not available. The max-sequence is used instead",
self->sequence, self->type);
- self->sequence = count - 1;
+ self->sequence = 0;
+ return -EINVAL;
}
self->sensor = list[self->sequence];
if (new_type != self->type) {
/* Different sensor is being used. Clean it up! */
- ret = _ts_clean_up_handle (self);
+ if (self->configured)
+ ret = _ts_clean_up_handle (self);
if (ret) {
GST_ERROR_OBJECT (self, "_ts_clean_up_handle() returns %d", ret);
if (self->sequence != new_sequence) {
/* Different sensor is being used. Clean it up! */
- ret = _ts_clean_up_handle (self);
+ if (self->configured)
+ ret = _ts_clean_up_handle (self);
if (ret) {
GST_ERROR_OBJECT (self, "_ts_clean_up_handle() returns %d", ret);
self->freq_n = gst_value_get_fraction_numerator (value);
self->freq_d = gst_value_get_fraction_denominator (value);
+ if (self->freq_n < 0)
+ self->freq_n = 0;
+ if (self->freq_d < 1)
+ self->freq_d = 1;
+
silent_debug ("Set operating frequency %d/%d --> %d/%d",
n, d, self->freq_n, self->freq_d);
g_value_set_enum (value, self->mode);
break;
case PROP_FREQ:
+ if (self->freq_d < 1)
+ self->freq_d = 1;
+ if (self->freq_n < 0)
+ self->freq_n = 0;
gst_value_set_fraction (value, self->freq_n, self->freq_d);
break;
default:
/* 2. Configure handle / context */
ret = _ts_configure_handle (self);
+ if (ret) {
+ retval = FALSE;
+ goto exit;
+ }
g_assert (self->configured == TRUE);
/** @todo TBD. Let's assume each frame has a fixed size */
static GstCaps *
_ts_get_gstcaps_from_conf (GstTensorSrcTIZENSENSOR * self)
{
- TizenSensorSpec *spec;
+ const GstTensorInfo *spec;
gchar *tensor;
GstCaps *retval;
- spec = g_hash_table_lookup (tizensensors, (gpointer) self->type);
+ /** @bug the retrieved spec is not valid...? */
+ spec = g_hash_table_lookup (tizensensors, GINT_TO_POINTER (self->type));
if (FALSE == self->configured || SENSOR_ALL == self->type || NULL == spec) {
tensor = g_strdup_printf ("other/tensor; other/tensors, num_tensors=1");
} else {
- const gchar *typestr = gst_tensor_get_type_string (spec->tinfo.type);
- gchar *dimstr = gst_tensor_get_dimension_string (spec->tinfo.dimension);
+ const gchar *typestr = gst_tensor_get_type_string (spec->type);
+ gchar *dimstr = gst_tensor_get_dimension_string (spec->dimension);
g_assert (typestr && typestr[0]);
tensor = g_strdup_printf ("other/tensor, dimension=%s, type=%s ; "
"other/tensors, num_tensors=1, dimensions=%s, type=%s",
}
gst_buffer_append_memory (buf, mem);
+ _UNLOCK (self);
retval = gst_tensor_src_tizensensor_fill (src, offset, buffer_size, buf);
+ _LOCK (self);
if (retval != GST_FLOW_OK)
goto exit;
GstMemory *mem;
GstMapInfo map;
+ if (self->freq_n > 0) {
+ gulong usec;
+ g_assert (self->freq_d > 0);
+
+ usec = ((gulong) self->freq_d * G_USEC_PER_SEC) / self->freq_n;
+ /** @tood Substract latency of itself */
+ g_usleep (usec);
+ }
+
_LOCK (self);
if (FALSE == self->configured) {
pts -= diff;
GST_BUFFER_PTS (buffer) = pts;
} else {
- GST_ERROR_OBJECT (self,
- "The given buffer for fill function does not have a valid timestamp.");
- retval = GST_FLOW_ERROR;
- goto exit_unmap;
+ /* There is no valid timestamp. Override the timestamp */
+
+ /** @todo NYI. Need research on this matter */
}
GST_BUFFER_PTS (buffer) = event.timestamp;
%if 0%{?enable_nnfw_r}
./tests/tizen_nnfw_runtime/unittest_nnfw_runtime_raw --gst-plugin-path=. --gtest_output="xml:unittest_nnfw_runtime_raw.xml"
%endif
+%if %{with tizen}
+ ln -s ext/nnstreamer/tensor_source/*.so .
+ ./tests/tizen_capi/unittest_tizen_sensor --gst-plugin-path=. --gtest_output="xml:unittest_tizen_sensor.xml"
+%endif
popd
pushd tests
ssat -n
--- /dev/null
+/**
+ * @file dummy_sensor.h
+ * @date 28 Nov 2019
+ * @brief Dummy Tizen Sensor API support for unit tests.
+ * @see https://github.com/nnsuite/nnstreamer
+ * @author MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug No known bugs
+ * @details The sensor framework source plugin should be
+ * linked with dummy_sensor.
+ */
+
+#include <time.h>
+
+#include "dummy_sensor.h"
+#include <errno.h>
+#include <string.h>
+
+static void init_timestamps (void) __attribute__ ((constructor));
+
+static sensor_s sensors[][3] = {
+ /* 0 = SENSOR_ACCELEROMETER */
+ {{.type = SENSOR_ACCELEROMETER,.id = 0,.listeners = NULL, .last_recorded =
+ {0}},
+ {.type = SENSOR_ACCELEROMETER,.id = 1,.listeners = NULL, .last_recorded =
+ {0}},
+ {.type = SENSOR_ACCELEROMETER,.id = 2,.listeners = NULL, .last_recorded =
+ {0}}},
+ {{0}, {0}, {0}}, /* 1 */
+ {{0}, {0}, {0}}, /* 1 */
+ {{0}, {0}, {0}}, /* 2 */
+ {{0}, {0}, {0}}, /* 3 */
+ {{0}, {0}, {0}}, /* 4 */
+ {{0}, {0}, {0}}, /* 5 */
+ {{0}, {0}, {0}}, /* 6 */
+ {{.type = SENSOR_LIGHT,.id = 0,.listeners = NULL, .last_recorded = {0}},
+ {.type = SENSOR_LIGHT,.id = 1,.listeners = NULL, .last_recorded = {0}},
+ {.type = SENSOR_LIGHT,.id = 2,.listeners = NULL, .last_recorded = {0}}}
+};
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_is_supported (sensor_type_e type, bool * supported)
+{
+ if (type == SENSOR_ACCELEROMETER || type == SENSOR_LIGHT)
+ *supported = true;
+ else
+ *supported = false;
+ return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_get_default_sensor (sensor_type_e type, sensor_h * sensor)
+{
+ bool supported;
+
+ sensor_is_supported (type, &supported);
+ if (supported == false) {
+ return -EINVAL;
+ }
+
+ *sensor = &(sensors[type][0]);
+ return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_get_sensor_list (sensor_type_e type, sensor_h ** list, int *sensor_count)
+{
+ bool supported;
+ int i;
+
+ sensor_is_supported (type, &supported);
+ if (supported == false) {
+ *list = NULL;
+ *sensor_count = 0;
+ return 0;
+ }
+
+ *list = g_new0 (sensor_h, 3);
+ for (i = 0; i < 3; i++)
+ (*list)[i] = &(sensors[type][i]);
+ *sensor_count = 3;
+
+ return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_get_type (sensor_h sensor, sensor_type_e * type)
+{
+ sensor_s *ptr = sensor;
+ bool supported;
+
+ sensor_is_supported (ptr->type, &supported);
+ if (supported)
+ *type = ptr->type;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_create_listener (sensor_h sensor, sensor_listener_h * listener)
+{
+ sensor_listener_s *ptr = g_new0 (sensor_listener_s, 1);
+ sensor_type_e type;
+ GHashTable *table;
+
+ ptr->is_listening = 0;
+ ptr->listening = sensor;
+
+ if (NULL == sensor || sensor_get_type (sensor, &type) < 0 ||
+ ptr->listening->id > 3)
+ return -EINVAL;
+
+ if (NULL == ptr->listening->listeners) {
+ ptr->listening->listeners = g_hash_table_new (NULL, NULL);
+ }
+ table = ptr->listening->listeners;
+
+ g_hash_table_add (table, ptr);
+
+ *listener = ptr;
+ return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_destroy_listener (sensor_listener_h listener)
+{
+ bool removed = false;
+ sensor_listener_s *l = listener;
+ sensor_s *s;
+ GHashTable *table;
+
+ if (l == NULL)
+ return -EINVAL;
+
+ s = l->listening;
+ if (s == NULL)
+ return -EINVAL;
+
+ table = s->listeners;
+ if (table == NULL)
+ return -EINVAL;
+
+ removed = g_hash_table_remove (table, l);
+ if (removed == false)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_listener_start (sensor_listener_h listener)
+{
+ sensor_listener_s *ptr = listener;
+ if (NULL == listener)
+ return -EINVAL;
+
+ ptr->is_listening = 1;
+ return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_listener_stop (sensor_listener_h listener)
+{
+ sensor_listener_s *ptr = listener;
+ if (NULL == listener)
+ return -EINVAL;
+
+ ptr->is_listening = 0;
+ return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_listener_read_data (sensor_listener_h listener, sensor_event_s * event)
+{
+ sensor_listener_s *ptr = listener;
+ sensor_s *s;
+
+ if (NULL == listener || NULL == event)
+ return -EINVAL;
+
+ s = ptr->listening;
+ if (NULL == s || !ptr->is_listening)
+ return -EINVAL;
+
+ memcpy (event, &(s->last_recorded), sizeof (sensor_event_s));
+
+ return 0;
+}
+
+/**
+ * @brief Dummy Tizen Sensor.
+ */
+int
+dummy_publish (sensor_h sensor, sensor_event_s value)
+{
+ sensor_s *s;
+
+ if (NULL == sensor)
+ return -EINVAL;
+
+ s = sensor;
+
+ memcpy (&(s->last_recorded), &value, sizeof (sensor_event_s));
+
+ if (s->last_recorded.timestamp == 0) {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ s->last_recorded.timestamp = ((unsigned long long)(t.tv_sec)*1000000LL +
+ (unsigned long long)(t.tv_nsec)/1000);
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Initialize default timestamps values to avoid runtime errors in Gst
+ */
+static void init_timestamps (void)
+{
+ struct timespec t;
+ unsigned long long ts;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ ts = ((unsigned long long)(t.tv_sec)*1000000LL +
+ (unsigned long long)(t.tv_nsec)/1000);
+ sensors[0][0].last_recorded.timestamp = ts;
+ sensors[0][1].last_recorded.timestamp = ts;
+ sensors[0][2].last_recorded.timestamp = ts;
+ sensors[7][0].last_recorded.timestamp = ts;
+ sensors[7][1].last_recorded.timestamp = ts;
+ sensors[7][2].last_recorded.timestamp = ts;
+}
--- /dev/null
+/**
+ * @file dummy_sensor.h
+ * @date 28 Nov 2019
+ * @brief Dummy Tizen Sensor API support for unit tests.
+ * @see https://github.com/nnsuite/nnstreamer
+ * @author MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug No known bugs
+ * @details The sensor framework source plugin should be
+ * linked with dummy_sensor.
+ *
+ * This will simply connect values from publish()
+ * to listener().
+ *
+ * This has sensor-fw APIs that are used by
+ * nnstreamer only.
+ */
+#ifndef __DUMMY_SENSOR_H__
+#define __DUMMY_SENSOR_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#include <stdint.h>
+#include <stdbool.h>
+#include <glib.h>
+
+#include <errno.h>
+#include <tizen_error.h>
+
+
+typedef enum
+{
+ SENSOR_ALL = -1, /**< All sensors. This can be used to retrieve #sensor_h for all available sensors. */
+ SENSOR_ACCELEROMETER, /**< Accelerometer */
+ SENSOR_GRAVITY, /**< Gravity sensor */
+ SENSOR_LINEAR_ACCELERATION, /**< Linear acceleration sensor */
+ SENSOR_MAGNETIC, /**< Magnetic sensor */
+ SENSOR_ROTATION_VECTOR, /**< Rotation vector sensor */
+ SENSOR_ORIENTATION, /**< Orientation sensor */
+ SENSOR_GYROSCOPE, /**< Gyroscope */
+ SENSOR_LIGHT, /**< Light sensor */
+ SENSOR_PROXIMITY, /**< Proximity sensor */
+ SENSOR_PRESSURE, /**< Pressure sensor */
+ SENSOR_ULTRAVIOLET, /**< Ultraviolet sensor */
+ SENSOR_TEMPERATURE, /**< Temperature sensor */
+ SENSOR_HUMIDITY, /**< Humidity sensor */
+ SENSOR_HRM, /**< Heart-rate monitor @if MOBILE (Since 2.3.1) @endif
+ @n Privilege : http://tizen.org/privilege/healthinfo */
+ SENSOR_HRM_LED_GREEN, /**< Green LED sensor of HRM @if MOBILE (Since 2.3.1) @endif
+ @n Privilege : http://tizen.org/privilege/healthinfo */
+ SENSOR_HRM_LED_IR, /**< Infra-Red LED sensor of HRM @if MOBILE (Since 2.3.1) @endif
+ @n Privilege : http://tizen.org/privilege/healthinfo */
+ SENSOR_HRM_LED_RED, /**< Red LED sensor of HRM @if MOBILE (Since 2.3.1) @endif
+ @n Privilege : http://tizen.org/privilege/healthinfo */
+ SENSOR_GYROSCOPE_UNCALIBRATED, /**< Uncalibrated Gyroscope sensor
+ @if MOBILE (Since 2.4) @elseif WEARABLE (Since 2.3.2) @endif */
+ SENSOR_GEOMAGNETIC_UNCALIBRATED, /**< Uncalibrated Geomagnetic sensor
+ @if MOBILE (Since 2.4) @elseif WEARABLE (Since 2.3.2) @endif */
+ SENSOR_GYROSCOPE_ROTATION_VECTOR, /**< Gyroscope-based rotation vector sensor
+ @if MOBILE (Since 2.4) @elseif WEARABLE (Since 2.3.2) @endif */
+ SENSOR_GEOMAGNETIC_ROTATION_VECTOR, /**< Geomagnetic-based rotation vector sensor
+ @if MOBILE (Since 2.4) @elseif WEARABLE (Since 2.3.2) @endif */
+ SENSOR_SIGNIFICANT_MOTION = 0x100, /**< Significant motion sensor (Since 4.0) */
+ SENSOR_HUMAN_PEDOMETER = 0x300, /**< Pedometer (Since 3.0)
+ @n Privilege : http://tizen.org/privilege/healthinfo */
+ SENSOR_HUMAN_SLEEP_MONITOR, /**< Sleep monitor (Since 3.0)
+ @n Privilege : http://tizen.org/privilege/healthinfo */
+ SENSOR_HUMAN_SLEEP_DETECTOR, /**< Sleep detector (Since 3.0)
+ @n Privilege : http://tizen.org/privilege/healthinfo */
+ SENSOR_HUMAN_STRESS_MONITOR, /**< Stress monitor (Since 3.0)
+ @n Privilege : http://tizen.org/privilege/healthinfo */
+ SENSOR_LAST, /**< End of sensor enum values (Deprecated since 3.0) */
+ SENSOR_CUSTOM = 0x2710, /**< Custom sensor (Deprecated since 3.0) */
+} sensor_type_e;
+
+/**
+ * @brief Enumeration for errors.
+ * @since_tizen @if MOBILE 2.3 @elseif WEARABLE 2.3.1 @endif
+ */
+typedef enum {
+ SENSOR_ERROR_NONE = TIZEN_ERROR_NONE, /**< Successful */
+ SENSOR_ERROR_IO_ERROR = TIZEN_ERROR_IO_ERROR, /**< I/O error */
+ SENSOR_ERROR_INVALID_PARAMETER = TIZEN_ERROR_INVALID_PARAMETER, /**< Invalid parameter */
+ SENSOR_ERROR_NOT_SUPPORTED = TIZEN_ERROR_NOT_SUPPORTED, /**< Not supported */
+ SENSOR_ERROR_PERMISSION_DENIED = TIZEN_ERROR_PERMISSION_DENIED, /**< Permission denied */
+ SENSOR_ERROR_OUT_OF_MEMORY = TIZEN_ERROR_OUT_OF_MEMORY, /**< Out of memory */
+ SENSOR_ERROR_NO_DATA = TIZEN_ERROR_NO_DATA, /**< No data available
+ @if MOBILE (Since 3.0) @elseif WEARABLE (Since 2.3.2) @endif */
+ SENSOR_ERROR_NOT_NEED_CALIBRATION = TIZEN_ERROR_SENSOR | 0x03, /**< Sensor doesn't need calibration */
+ SENSOR_ERROR_OPERATION_FAILED = TIZEN_ERROR_SENSOR | 0x06, /**< Operation failed */
+ SENSOR_ERROR_NOT_AVAILABLE = TIZEN_ERROR_SENSOR | 0x07, /**< The sensor is supported, but currently not available
+ @if MOBILE (Since 3.0) @elseif WEARABLE (Since 2.3.2) @endif */
+} sensor_error_e;
+
+
+/* event should be exactly same with the original */
+typedef struct
+{
+ int accuracy; /**< Accuracy of sensor data */
+ unsigned long long timestamp; /**< Time when the sensor data was observed */
+ int value_count; /**< Number of sensor data values stored in #sensor_event_s::values */
+ float values[16]; /**< Sensor data values */
+} sensor_event_s;
+
+typedef struct {
+ sensor_type_e type;
+ uint32_t id;
+ GHashTable *listeners;
+ sensor_event_s last_recorded;
+} sensor_s;
+typedef void* sensor_h;
+
+typedef struct {
+ sensor_s *listening;
+ int is_listening;
+} sensor_listener_s;
+
+typedef void* sensor_listener_h;
+
+
+
+
+
+/* main */
+extern int
+sensor_is_supported (sensor_type_e type, bool * supported);
+
+extern int
+sensor_get_default_sensor (sensor_type_e type, sensor_h *sensor);
+
+extern int
+sensor_get_sensor_list (sensor_type_e type, sensor_h **list, int *sensor_count);
+
+extern int
+sensor_get_type (sensor_h sensor, sensor_type_e *type);
+
+
+
+/* listener */
+extern int
+sensor_create_listener (sensor_h sensor, sensor_listener_h *listener);
+
+extern int
+sensor_destroy_listener (sensor_listener_h listener);
+
+extern int
+sensor_listener_start (sensor_listener_h listener);
+
+extern int
+sensor_listener_stop (sensor_listener_h listener);
+
+extern int
+sensor_listener_read_data (sensor_listener_h listener, sensor_event_s *event);
+
+
+/* publish data */
+extern int
+dummy_publish (sensor_h sensor, sensor_event_s value);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* __DUMMY_SENSOR_H__ */
install_dir: unittest_install_dir
)
test('unittest_tizen_capi', unittest_tizen_capi, args: ['--gst-plugin-path=../..'])
+
+
+if get_option('enable-tizen-sensor')
+
+ unittest_sensor_helper = shared_library('capi-system-sensor',
+ ['dummy_sensor.c'],
+ dependencies: [glib_dep],
+ )
+ unittest_sensor_helper_dep = declare_dependency(
+ link_with: unittest_sensor_helper,
+ )
+
+ tizen_sensor_apptest_deps = [
+ gtest_dep,
+ glib_dep,
+ gst_dep,
+ gst_base_dep,
+ nnstreamer_dep,
+ nnstreamer_capi_dep,
+ unittest_sensor_helper_dep,
+ ]
+ unittest_tizen_sensor = executable('unittest_tizen_sensor',
+ ['unittest_tizen_sensor.cpp'],
+ dependencies: [tizen_sensor_apptest_deps],
+ install: get_option('install-test'),
+ install_dir: unittest_install_dir
+ )
+ test('unittest_tizen_sensor', unittest_tizen_sensor, args: ['--gst-plugin-path=../..'])
+endif
--- /dev/null
+/**
+ * @file sensor.h
+ * @date 28 Nov 2019
+ * @brief Tizen Sensor Framework Simulator
+ * @see https://github.com/nnsuite/nnstreamer
+ * @author MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug No known bugs
+ */
+#include "dummy_sensor.h"
--- /dev/null
+/**
+ * @file unittest_tizen_sensor.cpp
+ * @date 25 Nov 2019
+ * @brief Unit test for NNStreamer's tensor-src-tizensensor.
+ * @see https://github.com/nnsuite/nnstreamer
+ * @author MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug No known bugs
+ */
+
+#ifndef __TIZEN__
+/* These works only in Tizen */
+#error This unit test works only in Tizen. This needs Tizen Sensor Framework.
+#endif
+
+#include <glib.h>
+#include "dummy_sensor.h" /* Dummy Tizen Sensor Framework */
+#include <gtest/gtest.h>
+#include <gst/gst.h>
+#include <nnstreamer.h>
+#include <nnstreamer-capi-private.h>
+
+/**
+ * @brief Test pipeline creation of it
+ */
+TEST (tizensensor_as_source, virtual_sensor_create_01)
+{
+ gchar *pipeline;
+ sensor_event_s value;
+ sensor_h sensor;
+ GstElement *gstpipe;
+ GError *err = NULL;
+ int status = 0;
+
+ sensor_get_default_sensor (SENSOR_LIGHT, &sensor);
+
+ value.accuracy = 1;
+ value.timestamp = 0U;
+ value.value_count = 1;
+ value.values[0] = 0.01;
+ EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+ /* Create a nnstreamer pipeline */
+ pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_LIGHT sequence=0 num-buffers=3 ! fakesink");
+ gstpipe = gst_parse_launch (pipeline, &err);
+ if (gstpipe) {
+ status = 0;
+ gst_object_unref (gstpipe);
+ } else {
+ status = -1;
+ g_printerr("GST PARSE LAUNCH FAILED: [%s], %s\n",
+ pipeline, (err) ? err->message : "unknown reason");
+ g_clear_error (&err);
+ }
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ g_free (pipeline);
+}
+
+/**
+ * @brief Test pipeline creation.
+ */
+TEST (tizensensor_as_source, virtual_sensor_create_02)
+{
+ gchar *pipeline;
+ sensor_event_s value;
+ sensor_h sensor;
+ sensor_h *sensor_list;
+ GstElement *gstpipe;
+ int status = 0;
+ int count;
+
+ sensor_get_sensor_list (SENSOR_LIGHT, &sensor_list, &count);
+ EXPECT_EQ (count, 3);
+ sensor = sensor_list[2];
+ g_free (sensor_list);
+
+ value.accuracy = 1;
+ value.timestamp = 0U;
+ value.value_count = 1;
+ value.values[0] = 0.01;
+ EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+ /* Create a nnstreamer pipeline */
+ pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_ACCELEROMETER sequence=-1 num-buffers=3 ! fakesink");
+ gstpipe = gst_parse_launch (pipeline, NULL);
+ if (gstpipe) {
+ status = 0;
+ gst_object_unref (gstpipe);
+ } else {
+ status = -1;
+ }
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ g_free (pipeline);
+
+}
+
+#define MAX_VERIFY_DATA (256)
+typedef struct {
+ int cursor;
+ int num_data;
+ float golden[256][16];
+ ml_tensor_type_e type;
+ int dim0;
+ int checked;
+ int negative;
+} verify_data;
+
+/**
+ * @brief Test if the sensor-reading matches the golden values.
+ */
+static void callback_nns (const ml_tensors_data_h data,
+ const ml_tensors_info_h info, void *user_data)
+{
+ verify_data *vdata = (verify_data *) user_data;
+ int status;
+ unsigned int count;
+ ml_tensor_type_e type;
+ ml_tensor_dimension dimension;
+
+ void *raw_data;
+ size_t data_size;
+ float *dataptr;
+
+ status = ml_tensors_info_get_count (info, &count);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_EQ (count, 1);
+
+ status = ml_tensors_info_get_tensor_type (info, 0, &type);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_EQ (type, ML_TENSOR_TYPE_FLOAT32);
+
+ status = ml_tensors_info_get_tensor_dimension (info, 0, dimension);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_EQ (dimension[0], 1);
+
+ status = ml_tensors_data_get_tensor_data (data, 0, &raw_data, &data_size);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ dataptr = (float *) raw_data;
+
+ if (vdata->negative) {
+ EXPECT_FALSE (dataptr[0] == vdata->golden[vdata->cursor][0] ||
+ dataptr[0] == vdata->golden[vdata->cursor + 1][0]);
+ } else {
+ EXPECT_TRUE (dataptr[0] == vdata->golden[vdata->cursor][0] ||
+ dataptr[0] == vdata->golden[vdata->cursor + 1][0]);
+ }
+
+ if (dataptr[0] == vdata->golden[vdata->cursor + 1][0])
+ vdata->cursor += 1;
+
+ vdata->checked += 1;
+}
+
+/**
+ * @brief Test pipeline creation and sink
+ */
+TEST (tizensensor_as_source, virtual_sensor_flow_03)
+{
+ gchar *pipeline;
+ sensor_event_s value;
+ sensor_h sensor;
+ int status = 0;
+ int count;
+ ml_pipeline_h handle;
+ ml_pipeline_sink_h s_handle;
+ ml_pipeline_state_e state;
+ verify_data data;
+
+ status = sensor_get_default_sensor (SENSOR_LIGHT, &sensor);
+ EXPECT_EQ (status, 0);
+
+ value.accuracy = 1;
+ value.timestamp = 0U;
+ value.value_count = 1;
+ value.values[0] = 0.01;
+ EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+ data.checked = 0;
+ data.dim0 = 1;
+ data.type = ML_TENSOR_TYPE_FLOAT32;
+ data.cursor = 0;
+ data.num_data = 3;
+ data.golden[0][0] = 0.01;
+ data.golden[1][0] = 1.01;
+ data.golden[2][0] = 3.31;
+ data.negative = 0;
+
+ /* Create a nnstreamer pipeline */
+ pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_LIGHT sequence=-1 num-buffers=50 framerate=100/1 ! tensor_sink name=getv");
+ status = ml_pipeline_construct (pipeline, NULL, NULL, &handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ status = ml_pipeline_sink_register (handle, "getv", callback_nns, &data, &s_handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (s_handle != NULL);
+
+ status = ml_pipeline_start (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ g_usleep (10000); /* 10ms. Wait a bit. */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ /* At this moment, it can be READY, PAUSED, or PLAYING */
+ EXPECT_NE (state, ML_PIPELINE_STATE_UNKNOWN);
+ EXPECT_NE (state, ML_PIPELINE_STATE_NULL);
+
+ count = 0;
+ while (state != ML_PIPELINE_STATE_PLAYING) {
+ g_usleep(1000); /* 1ms */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ count++;
+ EXPECT_LE (count, 500);
+ if (count >= 500)
+ break;
+ }
+ g_usleep(10000); /* Let a frame or more flow */
+ value.values[0] = 1.01;
+ EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+ g_usleep (100000); /* 100ms. Let a few frames flow. */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (state == ML_PIPELINE_STATE_PLAYING ||
+ state == ML_PIPELINE_STATE_PAUSED);
+ EXPECT_GT (data.checked, 1);
+
+ status = ml_pipeline_stop (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ g_usleep (10000); /* 10ms. Wait a bit. */
+
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_EQ (state, ML_PIPELINE_STATE_PAUSED);
+
+ status = ml_pipeline_sink_unregister (s_handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ status = ml_pipeline_destroy (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ g_free (pipeline);
+}
+
+/**
+ * @brief Test pipeline creation and sink
+ */
+TEST (tizensensor_as_source, virtual_sensor_flow_04)
+{
+ gchar *pipeline;
+ sensor_event_s value;
+ sensor_h sensor;
+ sensor_h *sensor_list;
+ int status = 0;
+ int count;
+ ml_pipeline_h handle;
+ ml_pipeline_sink_h s_handle;
+ ml_pipeline_state_e state;
+ verify_data data;
+
+ sensor_get_sensor_list (SENSOR_LIGHT, &sensor_list, &count);
+ EXPECT_EQ (count, 3);
+ sensor = sensor_list[2];
+ g_free (sensor_list);
+
+ value.accuracy = 1;
+ value.timestamp = 0U;
+ value.value_count = 1;
+ value.values[0] = 0.01;
+ EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+ data.checked = 0;
+ data.dim0 = 1;
+ data.type = ML_TENSOR_TYPE_FLOAT32;
+ data.cursor = 0;
+ data.num_data = 3;
+ data.golden[0][0] = 0.01;
+ data.golden[1][0] = 1.01;
+ data.golden[2][0] = 3.31;
+ data.negative = 0;
+
+ /* Create a nnstreamer pipeline */
+ pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_LIGHT sequence=2 num-buffers=50 framerate=100/1 ! tensor_sink name=getv");
+ status = ml_pipeline_construct (pipeline, NULL, NULL, &handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ status = ml_pipeline_sink_register (handle, "getv", callback_nns, &data, &s_handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (s_handle != NULL);
+
+ status = ml_pipeline_start (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ g_usleep (10000); /* 10ms. Wait a bit. */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ /* At this moment, it can be READY, PAUSED, or PLAYING */
+ EXPECT_NE (state, ML_PIPELINE_STATE_UNKNOWN);
+ EXPECT_NE (state, ML_PIPELINE_STATE_NULL);
+
+ count = 0;
+ while (state != ML_PIPELINE_STATE_PLAYING) {
+ g_usleep(10000); /* 10ms */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ count++;
+ EXPECT_LE (count, 50);
+ if (count >= 50)
+ break;
+ }
+ g_usleep(10000); /* Let a frame or more flow */
+ value.values[0] = 1.01;
+ EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+ g_usleep (100000); /* 100ms. Let a few frames flow. */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (state == ML_PIPELINE_STATE_PLAYING ||
+ state == ML_PIPELINE_STATE_PAUSED);
+ EXPECT_GT (data.checked, 1);
+
+ status = ml_pipeline_stop (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ g_usleep (10000); /* 10ms. Wait a bit. */
+
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_EQ (state, ML_PIPELINE_STATE_PAUSED);
+
+ status = ml_pipeline_sink_unregister (s_handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ status = ml_pipeline_destroy (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ g_free (pipeline);
+}
+
+/**
+ * @brief Test pipeline creation and sink (negative)
+ */
+TEST (tizensensor_as_source, virtual_sensor_flow_05_n)
+{
+ gchar *pipeline;
+ sensor_event_s value;
+ sensor_h sensor;
+ sensor_h *sensor_list;
+ int status = 0;
+ int count;
+ ml_pipeline_h handle;
+ ml_pipeline_sink_h s_handle;
+ ml_pipeline_state_e state;
+ verify_data data;
+
+ sensor_get_sensor_list (SENSOR_LIGHT, &sensor_list, &count);
+ EXPECT_EQ (count, 3);
+ sensor = sensor_list[2];
+
+ value.accuracy = 1;
+ value.timestamp = 0U;
+ value.value_count = 1;
+ value.values[0] = 0.00;
+ EXPECT_EQ (dummy_publish (sensor_list[0], value), 0);
+ EXPECT_EQ (dummy_publish (sensor_list[1], value), 0);
+ EXPECT_EQ (dummy_publish (sensor_list[2], value), 0);
+ g_free (sensor_list);
+
+ value.accuracy = 1;
+ value.timestamp = 0U;
+ value.value_count = 1;
+ value.values[0] = 0.01;
+ EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+ data.checked = 0;
+ data.dim0 = 1;
+ data.type = ML_TENSOR_TYPE_FLOAT32;
+ data.cursor = 0;
+ data.num_data = 3;
+ data.golden[0][0] = 0.01;
+ data.golden[1][0] = 1.01;
+ data.golden[2][0] = 3.31;
+ data.negative = 1;
+
+ /* Create a nnstreamer pipeline */
+ pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_LIGHT sequence=1 num-buffers=50 framerate=100/1 ! tensor_sink name=getv");
+ status = ml_pipeline_construct (pipeline, NULL, NULL, &handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ status = ml_pipeline_sink_register (handle, "getv", callback_nns, &data, &s_handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (s_handle != NULL);
+
+ status = ml_pipeline_start (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ g_usleep (10000); /* 10ms. Wait a bit. */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ /* At this moment, it can be READY, PAUSED, or PLAYING */
+ EXPECT_NE (state, ML_PIPELINE_STATE_UNKNOWN);
+ EXPECT_NE (state, ML_PIPELINE_STATE_NULL);
+
+ count = 0;
+ while (state != ML_PIPELINE_STATE_PLAYING) {
+ g_usleep(10000); /* 10ms */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ count++;
+ EXPECT_LE (count, 50);
+ if (count >= 50)
+ break;
+ }
+ g_usleep(10000); /* Let a frame or more flow */
+ value.values[0] = 1.01;
+ EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+ g_usleep (100000); /* 100ms. Let a few frames flow. */
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (state == ML_PIPELINE_STATE_PLAYING ||
+ state == ML_PIPELINE_STATE_PAUSED);
+ EXPECT_GT (data.checked, 1);
+
+ status = ml_pipeline_stop (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ g_usleep (10000); /* 10ms. Wait a bit. */
+
+ status = ml_pipeline_get_state (handle, &state);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_EQ (state, ML_PIPELINE_STATE_PAUSED);
+
+ status = ml_pipeline_sink_unregister (s_handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ status = ml_pipeline_destroy (handle);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ g_free (pipeline);
+}
+/**
+ * @brief Main GTest
+ */
+int main (int argc, char **argv)
+{
+ int result;
+
+ testing::InitGoogleTest (&argc, argv);
+ set_feature_state (1);
+
+ gst_init (&argc, &argv);
+ result = RUN_ALL_TESTS ();
+
+ set_feature_state (-1);
+ return result;
+}