From 07d627271889e9f1503ad2da742d688181979d79 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Wed, 13 Mar 2019 17:11:37 +0900 Subject: [PATCH] [Tizen/API] Implement start/stop/getstate/destroy with testcases Implement Tizen-CAPI, "start/stop/getstate/destroy" for a nnstreamer pipeline. Add testcases for them. Signed-off-by: MyungJoo Ham --- tests/tizen_capi/meson.build | 3 +- tests/tizen_capi/unittest_tizen_capi.cpp | 40 +++++++++-- tizen-api/include/tizen-api-private.h | 2 +- tizen-api/include/tizen-api.h | 10 ++- tizen-api/src/tizen-api-pipeline.c | 117 ++++++++++++++++++++++++++----- 5 files changed, 147 insertions(+), 25 deletions(-) diff --git a/tests/tizen_capi/meson.build b/tests/tizen_capi/meson.build index b69c5ec..5521ddd 100644 --- a/tests/tizen_capi/meson.build +++ b/tests/tizen_capi/meson.build @@ -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', diff --git a/tests/tizen_capi/unittest_tizen_capi.cpp b/tests/tizen_capi/unittest_tizen_capi.cpp index 3ba9570..eb5ccdb 100644 --- a/tests/tizen_capi/unittest_tizen_capi.cpp +++ b/tests/tizen_capi/unittest_tizen_capi.cpp @@ -9,6 +9,7 @@ #include #include +#include /** * @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); } diff --git a/tizen-api/include/tizen-api-private.h b/tizen-api/include/tizen-api-private.h index 4207de9..54f25d9 100644 --- a/tizen-api/include/tizen-api-private.h +++ b/tizen-api/include/tizen-api-private.h @@ -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 */ diff --git a/tizen-api/include/tizen-api.h b/tizen-api/include/tizen-api.h index 604ba35..fa1ce63 100644 --- a/tizen-api/include/tizen-api.h +++ b/tizen-api/include/tizen-api.h @@ -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); diff --git a/tizen-api/src/tizen-api-pipeline.c b/tizen-api/src/tizen-api-pipeline.c index 01448e4..73d97ab 100644 --- a/tizen-api/src/tizen-api-pipeline.c +++ b/tizen-api/src/tizen-api-pipeline.c @@ -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; } -- 2.7.4