[Tizen/API] Implement start/stop/getstate/destroy with testcases
authorMyungJoo Ham <myungjoo.ham@samsung.com>
Wed, 13 Mar 2019 08:11:37 +0000 (17:11 +0900)
committerMyungJoo Ham <myungjoo.ham@samsung.com>
Fri, 22 Mar 2019 07:57:37 +0000 (16:57 +0900)
Implement Tizen-CAPI, "start/stop/getstate/destroy" for a nnstreamer pipeline.
Add testcases for them.

Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
tests/tizen_capi/meson.build
tests/tizen_capi/unittest_tizen_capi.cpp
tizen-api/include/tizen-api-private.h
tizen-api/include/tizen-api.h
tizen-api/src/tizen-api-pipeline.c

index b69c5ec..5521ddd 100644 (file)
@@ -2,7 +2,8 @@ tizen_apptest_deps = [
   capi_base_common_dep,
   dlog_dep,
   tizen_capi_dep,
-  gtest_dep
+  gtest_dep,
+  glib_dep
 ]
 
 unittest_tizen_capi = executable('unittest_tizen_capi',
index 3ba9570..eb5ccdb 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <tizen-api.h>
 #include <gtest/gtest.h>
+#include <glib.h>
 
 /**
  * @brief Test NNStreamer pipeline construct & destruct
@@ -18,11 +19,9 @@ TEST (nnstreamer_capi_construct_destruct, dummy_01)
   const char *pipeline = "videotestsrc num_buffers=2 ! fakesink";
   nns_pipeline_h handle;
   int status = nns_pipeline_construct (pipeline, &handle);
-
   EXPECT_EQ (status, NNS_ERROR_NONE);
 
   status = nns_pipeline_destroy (handle);
-
   EXPECT_EQ (status, NNS_ERROR_NONE);
 }
 
@@ -34,11 +33,9 @@ TEST (nnstreamer_capi_construct_destruct, dummy_02)
   const char *pipeline = "videotestsrc num_buffers=2 ! videoconvert ! videoscale ! video/x-raw,format=RGBx,width=224,height=224 ! tensor_converter ! fakesink";
   nns_pipeline_h handle;
   int status = nns_pipeline_construct (pipeline, &handle);
-
   EXPECT_EQ (status, NNS_ERROR_NONE);
 
   status = nns_pipeline_destroy (handle);
-
   EXPECT_EQ (status, NNS_ERROR_NONE);
 }
 
@@ -50,11 +47,44 @@ TEST (nnstreamer_capi_construct_destruct, dummy_03)
   const char *pipeline = "videotestsrc num_buffers=2 ! videoconvert ! videoscale ! video/x-raw,format=RGBx,width=224,height=224 ! tensor_converter ! valve name=valvex ! tensor_sink name=sinkx";
   nns_pipeline_h handle;
   int status = nns_pipeline_construct (pipeline, &handle);
-
   EXPECT_EQ (status, NNS_ERROR_NONE);
 
   status = nns_pipeline_destroy (handle);
+  EXPECT_EQ (status, NNS_ERROR_NONE);
+}
+
+/**
+ * @brief Test NNStreamer pipeline construct & destruct
+ */
+TEST (nnstreamer_capi_playstop, dummy_01)
+{
+  const char *pipeline = "videotestsrc is-live=true num-buffers=30 framerate=60/1 ! videoconvert ! videoscale ! video/x-raw,format=RGBx,width=224,height=224 ! tensor_converter ! valve name=valvex ! valve name=valvey ! input-selector name=is01 ! tensor_sink name=sinkx";
+  nns_pipeline_h handle;
+  nns_pipeline_state state;
+  int status = nns_pipeline_construct (pipeline, &handle);
+  EXPECT_EQ (status, NNS_ERROR_NONE);
+
+  status = nns_pipeline_start (handle);
+  EXPECT_EQ (status, NNS_ERROR_NONE);
+  status = nns_pipeline_getstate(handle, &state);
+  EXPECT_EQ (status, NNS_ERROR_NONE); /* At this moment, it can be READY, PAUSED, or PLAYING */
+  EXPECT_NE (state, NNS_PIPELINE_UNKNOWN);
+  EXPECT_NE (state, NNS_PIPELINE_NULL);
+
+  g_usleep(50000); /* 50ms. Let a few frames flow. */
+  status = nns_pipeline_getstate(handle, &state);
+  EXPECT_EQ (status, NNS_ERROR_NONE);
+  EXPECT_EQ (state, NNS_PIPELINE_PLAYING);
+
+  status = nns_pipeline_stop (handle);
+  EXPECT_EQ (status, NNS_ERROR_NONE);
+  g_usleep(50000); /* 50ms. Let a few frames flow. */
 
+  status = nns_pipeline_getstate(handle, &state);
+  EXPECT_EQ (status, NNS_ERROR_NONE);
+  EXPECT_EQ (state, NNS_PIPELINE_PAUSED);
+
+  status = nns_pipeline_destroy (handle);
   EXPECT_EQ (status, NNS_ERROR_NONE);
 }
 
index 4207de9..54f25d9 100644 (file)
@@ -61,7 +61,7 @@ typedef struct _nns_pipeline nns_pipeline;
 typedef struct _element {
   GstElement *element; /**< The Sink/Src/Valve/Switch element */
   nns_pipeline *pipe; /**< The main pipeline */
-  const char *name;
+  char *name;
   elementType type;
   GstPad *src;
   GstPad *sink; /**< Unref this at destroy */
index 604ba35..fa1ce63 100644 (file)
@@ -76,7 +76,7 @@ typedef enum {
   NNS_ERROR_NONE                               = TIZEN_ERROR_NONE, /**< Success! */
   NNS_ERROR_INVALID_PARAMETER                  = TIZEN_ERROR_INVALID_PARAMETER, /**< Invalid parameter */
   NNS_ERROR_NOT_SUPPORTED                      = TIZEN_ERROR_NOT_SUPPORTED, /**< The feature is not supported */
-  NNS_ERROR_PIPELINE_FAIL                      = TIZEN_ERROR_STREAMS_PIPE, /**< Cannot create Gstreamer pipeline. */
+  NNS_ERROR_PIPELINE_FAIL                      = TIZEN_ERROR_STREAMS_PIPE, /**< Cannot create or access Gstreamer pipeline. */
 } nns_error_e;
 
 /**
@@ -165,6 +165,8 @@ int nns_pipeline_construct (const char *pipeline_description, nns_pipeline_h *pi
  * @param[in] pipe The pipeline to be destroyed.
  * @return @c 0 on success. otherwise a negative error value
  * @retval #NNS_ERROR_NONE Successful
+ * @retval #NNS_ERROR_PIPELINE_FAIL Fail. Cannot access the pipeline status.
+ * @retval #NNS_ERROR_INVALID_PARAMETER Fail. The parameter is invalid (pipe is NULL?)
  */
 int nns_pipeline_destroy (nns_pipeline_h pipe);
 
@@ -176,6 +178,8 @@ int nns_pipeline_destroy (nns_pipeline_h pipe);
  * @param[out] state The pipeline state.
  * @return @c 0 on success. otherwise a negative error value
  * @retval #NNS_ERROR_NONE Successful
+ * @retval #NNS_ERROR_INVALID_PARAMETER Given parameter is invalid. (pipe is NULL?)
+ * @retval #NNS_ERROR_PIPELINE_FAIL Failed to get state from the pipeline.
  */
 int nns_pipeline_getstate (nns_pipeline_h pipe, nns_pipeline_state *state);
 
@@ -185,20 +189,24 @@ int nns_pipeline_getstate (nns_pipeline_h pipe, nns_pipeline_state *state);
 /**
  * @brief Start the pipeline
  * @detail The pipeline handle returned by nns_construct_pipeline (pipe) is started.
+ *         Note that this is asynchronous function. State might be "pending".
  * @since_tizen 5.5
  * @param[in] pipe The pipeline to be started.
  * @return @c 0 on success. otherwise a negative error value
  * @retval #NNS_ERROR_NONE Successful
+ * @retval #NNS_ERROR_PIPELINE_FAIL Failed to start.
  */
 int nns_pipeline_start (nns_pipeline_h pipe);
 
 /**
  * @brief Stop the pipeline
  * @detail The pipeline handle returned by nns_construct_pipeline (pipe) is stopped.
+ *         Note that this is asynchronous function. State might be "pending".
  * @since_tizen 5.5
  * @param[in] pipe The pipeline to be stopped.
  * @return @c 0 on success. otherwise a negative error value
  * @retval #NNS_ERROR_NONE Successful
+ * @retval #NNS_ERROR_PIPELINE_FAIL Failed to start.
  */
 int nns_pipeline_stop (nns_pipeline_h pipe);
 
index 01448e4..73d97ab 100644 (file)
@@ -60,6 +60,8 @@ cb_sink_event (GstElement * e, GstBuffer * b, gpointer data)
 {
   element *elem = data;
 
+  /** @todo CRITICAL if the pipeline is being killed, don't proceed! */
+
   GstMemory *mem[NNS_TENSOR_SIZE_LIMIT];
   GstMapInfo info[NNS_TENSOR_SIZE_LIMIT];
   guint i;
@@ -154,6 +156,31 @@ cb_sink_event (GstElement * e, GstBuffer * b, gpointer data)
 }
 
 /**
+ * @brief Private function for nns_pipeline_destroy, cleaning up nodes in namednodes
+ */
+static void
+cleanup_node (gpointer data)
+{
+  element *e = data;
+  g_mutex_lock (&e->lock);
+  g_free (e->name);
+  if (e->src)
+    gst_object_unref (e->src);
+  if (e->sink)
+    gst_object_unref (e->sink);
+
+  /** @todo CRITICAL. Stop the handle callbacks if they are running/ready */
+  if (e->handles)
+    g_list_free_full (e->handles, g_free);
+  e->handles = NULL;
+
+  g_mutex_unlock (&e->lock);
+  g_mutex_clear (&e->lock);
+
+  g_free (e);
+}
+
+/**
  * @brief Construct the pipeline (more info in tizen-api.h)
  */
 int
@@ -200,7 +227,8 @@ nns_pipeline_construct (const char *pipeline_description, nns_pipeline_h * pipe)
   g_mutex_init (&pipe_h->lock);
   g_mutex_lock (&pipe_h->lock);
 
-  pipe_h->namednodes = g_hash_table_new (g_str_hash, g_str_equal);
+  pipe_h->namednodes =
+      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, cleanup_node);
 
   it = gst_bin_iterate_elements (GST_BIN (pipeline));
   if (it != NULL) {
@@ -246,7 +274,7 @@ nns_pipeline_construct (const char *pipeline_description, nns_pipeline_h * pipe)
               }
 
               if (e != NULL)
-                g_hash_table_insert (pipe_h->namednodes, e, name);
+                g_hash_table_insert (pipe_h->namednodes, g_strdup (name), e);
             }
             g_free (name);
           }
@@ -270,9 +298,6 @@ nns_pipeline_construct (const char *pipeline_description, nns_pipeline_h * pipe)
     g_object_unref (outputs);
   }
 
-  /** @todo CRITICAL: Prepare the pipeline. Maybe as a forked thread */
-
-
   g_mutex_unlock (&pipe_h->lock);
   return ret;
 }
@@ -283,18 +308,45 @@ nns_pipeline_construct (const char *pipeline_description, nns_pipeline_h * pipe)
 int
 nns_pipeline_destroy (nns_pipeline_h pipe)
 {
-  /* nns_pipeline *p = pipe; */
-
-  /** @todo NYI */
+  nns_pipeline *p = pipe;
+  GstStateChangeReturn scret;
+  GstState state, pending;
+
+  if (p == NULL)
+    return NNS_ERROR_INVALID_PARAMETER;
+
+  g_mutex_lock (&p->lock);
+
+  /* if it's PLAYING, PAUSE it. */
+  scret = gst_element_get_state (p->element, &state, &pending, 10000000UL);     /* 10ms */
+  if (scret != GST_STATE_CHANGE_FAILURE && state == GST_STATE_PLAYING) {
+    /* Pause the pipeline if it's Playing */
+    scret = gst_element_set_state (p->element, GST_STATE_PAUSED);
+    if (scret == GST_STATE_CHANGE_FAILURE) {
+      g_mutex_unlock (&p->lock);
+      return NNS_ERROR_PIPELINE_FAIL;
+    }
+  }
 
-  /** @todo Pause the pipeline if it's Playing */
+  /** @todo Ensure all callbacks are gone. (kill'em all!) THIS IS CRITICAL! */
+  g_mutex_unlock (&p->lock);
+  g_usleep (50000);             /* do 50ms sleep until we have it implemented. Let them complete. And hope they don't call start(). */
+  g_mutex_lock (&p->lock);
 
-  /** @todo Ensure all callbacks are gone. (kill'em all!) */
+  /** Destroy registered callback handles */
+  g_hash_table_remove_all (p->namednodes);
 
-  /** @todo Stop (NULL State) the pipeline */
+  /** Stop (NULL State) the pipeline */
+  scret = gst_element_set_state (p->element, GST_STATE_NULL);
+  if (scret != GST_STATE_CHANGE_SUCCESS) {
+    g_mutex_unlock (&p->lock);
+    return NNS_ERROR_PIPELINE_FAIL;
+  }
 
-  /** @todo Destroy Everything */
+  gst_object_unref (p->element);
 
+  g_mutex_unlock (&p->lock);
+  g_mutex_clear (&p->lock);
   return NNS_ERROR_NONE;
 }
 
@@ -304,10 +356,25 @@ nns_pipeline_destroy (nns_pipeline_h pipe)
 int
 nns_pipeline_getstate (nns_pipeline_h pipe, nns_pipeline_state * state)
 {
-  /* *state = NNSAPI_UNKNOWN; */
-
-  /** @todo NYI */
+  nns_pipeline *p = pipe;
+  GstState _state;
+  GstState pending;
+  GstStateChangeReturn scret;
+  *state = NNS_PIPELINE_UNKNOWN;
+
+  if (p == NULL)
+    return NNS_ERROR_INVALID_PARAMETER;
+
+  g_mutex_lock (&p->lock);
+  scret =
+      gst_element_get_state (p->element, &_state, &pending,
+      GST_CLOCK_TIME_NONE);
+  g_mutex_unlock (&p->lock);
+
+  if (scret == GST_STATE_CHANGE_FAILURE)
+    return NNS_ERROR_PIPELINE_FAIL;
 
+  *state = _state;
   return NNS_ERROR_NONE;
 }
 
@@ -320,7 +387,15 @@ nns_pipeline_getstate (nns_pipeline_h pipe, nns_pipeline_state * state)
 int
 nns_pipeline_start (nns_pipeline_h pipe)
 {
-  /** @todo NYI */
+  nns_pipeline *p = pipe;
+  GstStateChangeReturn scret;
+
+  g_mutex_lock (&p->lock);
+  scret = gst_element_set_state (p->element, GST_STATE_PLAYING);
+  g_mutex_unlock (&p->lock);
+
+  if (scret == GST_STATE_CHANGE_FAILURE)
+    return NNS_ERROR_PIPELINE_FAIL;
 
   return NNS_ERROR_NONE;
 }
@@ -331,7 +406,15 @@ nns_pipeline_start (nns_pipeline_h pipe)
 int
 nns_pipeline_stop (nns_pipeline_h pipe)
 {
-  /** @todo NYI */
+  nns_pipeline *p = pipe;
+  GstStateChangeReturn scret;
+
+  g_mutex_lock (&p->lock);
+  scret = gst_element_set_state (p->element, GST_STATE_PAUSED);
+  g_mutex_unlock (&p->lock);
+
+  if (scret == GST_STATE_CHANGE_FAILURE)
+    return NNS_ERROR_PIPELINE_FAIL;
 
   return NNS_ERROR_NONE;
 }