[Converter] Add converter cusotm operation
authorGichan Jang <gichan2.jang@samsung.com>
Wed, 17 Mar 2021 07:31:44 +0000 (16:31 +0900)
committerjaeyun-jung <39614140+jaeyun-jung@users.noreply.github.com>
Thu, 25 Mar 2021 03:47:02 +0000 (12:47 +0900)
Add converter custom operation.
It is similar to cusom-easy of the tensor filter.
Get an input buffer and make output tensor.
It can be used to create tensors from not supported capabilities or
flexible data structure such as flexbuffer.

Signed-off-by: Gichan Jang <gichan2.jang@samsung.com>
debian/nnstreamer-dev.install
ext/nnstreamer/tensor_converter/tensor_converter_flexbuf.cc
gst/nnstreamer/include/meson.build
gst/nnstreamer/include/tensor_converter_custom.h [new file with mode: 0644]
gst/nnstreamer/include/tensor_typedef.h
gst/nnstreamer/nnstreamer_subplugin.c
gst/nnstreamer/nnstreamer_subplugin.h
gst/nnstreamer/tensor_converter/tensor_converter.c
gst/nnstreamer/tensor_converter/tensor_converter.h
packaging/nnstreamer.spec

index 0893dc7..0f95eee 100644 (file)
@@ -6,6 +6,7 @@
 /usr/include/nnstreamer/tensor_if.h
 /usr/include/nnstreamer/tensor_filter_custom.h
 /usr/include/nnstreamer/tensor_filter_custom_easy.h
+/usr/include/nnstreamer/tensor_converter_custom.h
 /usr/include/nnstreamer/tensor_typedef.h
 /usr/include/nnstreamer/tensor_filter_cpp.hh
 /usr/include/nnstreamer/nnstreamer_cppplugin_api_filter.hh
index 4ef8779..761e74d 100644 (file)
  *                          Blob | <tensor data>
  *                         }
  * }
+ *
+ * If you want to convert your own binary format of the flexbuffers to tensors,
+ * You can use custom mode of the tensor converter.
+ * @code
+ * // Define custom callback function
+ * GstBuffer * tensor_converter_custom_cb (GstBuffer *in_buf,
+ *     GstTensorsConfig *config) {
+ *   // Write a code to convert flexbuffers to tensors.
+ * }
+ *
+ * ...
+ * // Register custom callback function
+ * nnstreamer_converter_custom_register ("tconv", tensor_converter_custom_cb, NULL);
+ * ...
+ * // Use the custom tensor converter in a pipeline.
+ * // E.g., Pipeline of " ... (flexbuffers) ! tensor_converter mode=custom:tconv ! (tensors)... "
+ * ...
+ * // After everything is done.
+ * nnstreamer_converter_custom_unregister ("tconv");
+ * @endcode
  */
 
 #include <glib.h>
index cc762e3..8f40a1d 100644 (file)
@@ -4,6 +4,7 @@ nnst_common_headers = [
   'tensor_typedef.h',
   'tensor_filter_custom.h',
   'tensor_filter_custom_easy.h',
+  'tensor_converter_custom.h',
   'nnstreamer_plugin_api_filter.h',
   'nnstreamer_plugin_api_decoder.h',
   'nnstreamer_plugin_api_converter.h',
diff --git a/gst/nnstreamer/include/tensor_converter_custom.h b/gst/nnstreamer/include/tensor_converter_custom.h
new file mode 100644 (file)
index 0000000..4b3b3a9
--- /dev/null
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: LGPL-2.1-only */
+/**
+ * GStreamer/NNStreamer Tensor-Converter
+ * Copyright (C) 2021 Gichan Jang <gichan2.jang@samsung.com>
+ */
+/**
+ * @file       tensor_converter_custom.h
+ * @date       18 Mar 2021
+ * @brief      NNStreamer APIs for tensor_converter custom condition
+ * @see                https://github.com/nnstreamer/nnstreamer
+ * @author     Gichan Jang <gichan2.jang@samsung.com>
+ * @bug                No known bugs except for NYI items
+ *
+ */
+
+#ifndef __NNS_TENSOR_CONVERTER_CUSTOM_H__
+#define __NNS_TENSOR_CONVERTER_CUSTOM_H__
+
+#include <glib.h>
+#include <gst/gst.h>
+#include "tensor_typedef.h"
+
+G_BEGIN_DECLS
+/**
+ * @brief Convert to tensors as customized operation
+ * @param[in] in_buf the input stream buffer
+ * @param[out] config tensors config structure to be filled
+ * @return output buffer filled by user
+ */
+typedef GstBuffer * (* tensor_converter_custom) (GstBuffer *in_buf,
+    GstTensorsConfig *config);
+
+/**
+ * @brief Register the custom callback function.
+ * @param[in] name The name of tensor_converter custom callback function.
+ * @param[in] func The custom condition function body
+ * @param[in/out] data The internal data for the function
+ * @return 0 if success. -ERRNO if error.
+ */
+extern int
+nnstreamer_converter_custom_register (const gchar *name, tensor_converter_custom func, void *data);
+
+/**
+ * @brief Unregister the custom callback function.
+ * @param[in] name The registered name of tensor_converter custom callback function.
+ * @return 0 if success. -ERRNO if error.
+ */
+extern int
+nnstreamer_converter_custom_unregister (const gchar *name);
+
+G_END_DECLS
+#endif /*__NNS_TENSOR_CONVERTER_CUSTOM_H__*/
index 9e6e2ec..b9c2144 100644 (file)
@@ -135,7 +135,7 @@ typedef enum _nns_media_type
   _NNS_AUDIO = 1, /**< supposedly audio/x-raw */
   _NNS_TEXT = 2, /**< supposedly text/x-raw */
   _NNS_OCTET = 3, /**< supposedly application/octet-stream */
-  _NNS_MEDIA_PLUGINS = 0x1000, /**< external converters */
+  _NNS_MEDIA_ANY = 0x1000, /**< any media type (find proper external converter in tensor-converter element) */
 } media_type;
 
 /**
index a8d1107..5563f79 100644 (file)
@@ -72,6 +72,7 @@ static subpluginSearchLogic searchAlgorithm[] = {
   [NNS_SUBPLUGIN_DECODER] = NNS_SEARCH_FILENAME,
   [NNS_EASY_CUSTOM_FILTER] = NNS_SEARCH_FILENAME,
   [NNS_SUBPLUGIN_CONVERTER] = NNS_SEARCH_GETALL,
+  [NNS_CUSTOM_CONVERTER] = NNS_SEARCH_NO_OP,
   [NNS_IF_CUSTOM] = NNS_SEARCH_NO_OP,
   [NNS_SUBPLUGIN_END] = NNS_SEARCH_NO_OP,
 };
@@ -182,6 +183,7 @@ register_subplugin (subpluginType type, const char *name, const void *data)
     case NNS_EASY_CUSTOM_FILTER:
     case NNS_SUBPLUGIN_CONVERTER:
     case NNS_IF_CUSTOM:
+    case NNS_CUSTOM_CONVERTER:
       break;
     default:
       /* unknown sub-plugin type */
index 294258b..d22662c 100644 (file)
@@ -40,6 +40,7 @@ typedef enum {
   NNS_SUBPLUGIN_DECODER = NNSCONF_PATH_DECODERS,
   NNS_EASY_CUSTOM_FILTER = NNSCONF_PATH_EASY_CUSTOM_FILTERS,
   NNS_SUBPLUGIN_CONVERTER = NNSCONF_PATH_CONVERTERS,
+  NNS_CUSTOM_CONVERTER,
   NNS_IF_CUSTOM,
 
   NNS_SUBPLUGIN_END,
index 6be7ffc..50a4c0d 100644 (file)
@@ -133,7 +133,8 @@ enum
   PROP_FRAMES_PER_TENSOR,
   PROP_SET_TIMESTAMP,
   PROP_SUBPLUGINS,
-  PROP_SILENT
+  PROP_SILENT,
+  PROP_MODE
 };
 
 /**
@@ -274,6 +275,16 @@ gst_tensor_converter_class_init (GstTensorConverterClass * klass)
       g_param_spec_boolean ("silent", "Silent", "Produce verbose output",
           DEFAULT_SILENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * GstTensorConverter::mode:
+   *
+   * Generally this property is used to set tensor converter custom mode.
+   */
+  g_object_class_install_property (object_class, PROP_MODE,
+      g_param_spec_string ("mode", "Mode",
+          "Converter mode. e.g., mode=custom:<registered callback name>", "",
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   /* set src pad template */
   pad_caps =
       gst_caps_from_string (GST_TENSOR_CAP_DEFAULT "; "
@@ -354,6 +365,11 @@ gst_tensor_converter_init (GstTensorConverter * self)
   self->frame_size = 0;
   self->remove_padding = FALSE;
   self->externalConverter = NULL;
+  self->mode = _CONVERTER_MODE_NONE;
+  self->mode_option = NULL;
+  self->custom.func = NULL;
+  self->custom.data = NULL;
+
   gst_tensors_info_init (&self->tensors_info);
 
   self->adapter = gst_adapter_new ();
@@ -378,6 +394,10 @@ gst_tensor_converter_finalize (GObject * object)
     self->adapter = NULL;
   }
 
+  g_free (self->mode_option);
+  self->custom.func = NULL;
+  self->custom.data = NULL;
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -444,6 +464,34 @@ gst_tensor_converter_set_property (GObject * object, guint prop_id,
       self->silent = g_value_get_boolean (value);
       silent_debug ("Set silent = %d", self->silent);
       break;
+    case PROP_MODE:
+    {
+      const gchar *param = g_value_get_string (value);
+      const converter_custom_cb_s *ptr = NULL;
+      gchar **strv = g_strsplit_set (param, ":", -1);
+      self->custom.func = NULL;
+
+      if (g_strv_length (strv) < 2) {
+        nns_logw ("Tensor converter mode option is incorrect."
+            "Please specify mode option as <MODE>:<MODE_OPTION>");
+        g_strfreev (strv);
+        break;
+      }
+
+      if (g_ascii_strcasecmp (strv[0], "custom") == 0)
+        self->mode = _CONVERTER_MODE_CUSTOM;
+      self->mode_option = g_strdup (strv[1]);
+      g_strfreev (strv);
+
+      ptr = get_subplugin (NNS_CUSTOM_CONVERTER, self->mode_option);
+      if (!ptr) {
+        nns_logw ("Failed to find custom subplugin of the tensor_converter");
+        return;
+      }
+      self->custom.func = ptr->func;
+      self->custom.data = ptr->data;
+      break;
+    }
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -496,6 +544,16 @@ gst_tensor_converter_get_property (GObject * object, guint prop_id,
     case PROP_SILENT:
       g_value_set_boolean (value, self->silent);
       break;
+    case PROP_MODE:
+    {
+      gchar *mode_str = NULL;
+      if (self->mode_option == NULL)
+        mode_str = g_strdup ("");
+      else
+        mode_str = g_strdup_printf ("%s:%s", "custom", self->mode_option);
+      g_value_take_string (value, mode_str);
+      break;
+    }
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -577,7 +635,6 @@ gst_tensor_converter_sink_query (GstPad * pad, GstObject * parent,
   GstTensorConverter *self;
 
   self = GST_TENSOR_CONVERTER (parent);
-
   GST_DEBUG_OBJECT (self, "Received %s query: %" GST_PTR_FORMAT,
       GST_QUERY_TYPE_NAME (query), query);
 
@@ -995,35 +1052,62 @@ gst_tensor_converter_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
       g_assert ((buf_size % frame_size) == 0);
       frames_in = buf_size / frame_size;
       break;
-    case _NNS_MEDIA_PLUGINS:
-    {
-      GstTensorsConfig new_config;
-
-      if (self->externalConverter == NULL ||
-          self->externalConverter->convert == NULL)
-        return GST_FLOW_NOT_SUPPORTED;
-
-      inbuf =
-          self->externalConverter->convert (buf, &frame_size, &frames_in,
-          &new_config);
-
-      if (gst_tensors_config_is_equal (config, &new_config) != TRUE) {
-        if (gst_tensor_converter_update_caps (self, self->srcpad,
-                &new_config) == TRUE) {
-          self->tensors_config = new_config;
-        } else {
+    case _NNS_MEDIA_ANY:
+      if (self->mode == _CONVERTER_MODE_CUSTOM) {
+        GstTensorsConfig new_config;
+        if (self->custom.func == NULL) {
+          nns_loge
+              ("custom condition of the tensor_converter is not configured.");
           return GST_FLOW_ERROR;
         }
-      }
-
-      g_assert (inbuf != NULL);
-      g_assert (frame_size > 0);
+        inbuf = self->custom.func (buf, &new_config);
+        if (inbuf == NULL) {
+          nns_loge ("Failed to run custom tensor converter.");
+          gst_buffer_unref (buf);
+          return GST_FLOW_ERROR;
+        }
+        if (gst_tensors_config_is_equal (config, &new_config) != TRUE) {
+          if (gst_tensor_converter_update_caps (self, self->srcpad,
+                  &new_config) == TRUE) {
+            self->tensors_config = new_config;
+          } else {
+            gst_buffer_unref (buf);
+            return GST_FLOW_ERROR;
+          }
+        }
+        frames_in = 1;
+        frame_size = gst_buffer_get_size (inbuf);
 
-      if (inbuf != buf)
         gst_buffer_unref (buf);
+        break;
+      } else if (self->externalConverter && self->externalConverter->convert) {
+        GstTensorsConfig new_config;
+
+        inbuf =
+            self->externalConverter->convert (buf, &frame_size, &frames_in,
+            &new_config);
+
+        if (gst_tensors_config_is_equal (config, &new_config) != TRUE) {
+          if (gst_tensor_converter_update_caps (self, self->srcpad,
+                  &new_config) == TRUE) {
+            self->tensors_config = new_config;
+          } else {
+            return GST_FLOW_ERROR;
+          }
+        }
 
-      break;
-    }
+        g_assert (inbuf != NULL);
+        g_assert (frame_size > 0);
+
+        if (inbuf != buf)
+          gst_buffer_unref (buf);
+
+        break;
+      } else {
+        GST_ERROR_OBJECT (self, "Undefined behavior with type %d\n",
+            self->in_media_type);
+        return GST_FLOW_NOT_SUPPORTED;
+      }
     default:
       GST_ERROR_OBJECT (self, "Unsupported type %d\n", self->in_media_type);
       return GST_FLOW_ERROR;
@@ -1667,7 +1751,11 @@ gst_tensor_converter_parse_caps (GstTensorConverter * self,
   g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
 
   structure = gst_caps_get_structure (caps, 0);
-  in_type = gst_tensor_media_type_from_structure (structure);
+  if (self->mode == _CONVERTER_MODE_CUSTOM) {
+    in_type = _NNS_MEDIA_ANY;
+  } else {
+    in_type = gst_tensor_media_type_from_structure (structure);
+  }
 
   switch (in_type) {
     case _NNS_VIDEO:
@@ -1713,37 +1801,57 @@ gst_tensor_converter_parse_caps (GstTensorConverter * self,
         GST_ERROR_OBJECT (self, "Failed to configure tensors from octet info.");
         return FALSE;
       }
-
       break;
     default:
     {
-      /* if found, configure in_mdeia_type = _NNS_MEDIA_PLUGINS */
-      const gchar *struct_name;
+      if (self->mode == _CONVERTER_MODE_CUSTOM) {
+        gst_tensors_config_init (&config);
+
+        /* All tensor info should be updated later in chain function. */
+        config.info.info[0].type = _NNS_UINT8;
+        config.info.num_tensors = 1;
+        if (gst_tensor_parse_dimension ("1:1:1:1",
+                config.info.info[0].dimension) == 0) {
+          ml_loge ("Failed to set initial dimension for subplugin");
+          return FALSE;
+        }
 
-      struct_name = gst_structure_get_name (structure);
+        if (gst_structure_has_field (structure, "framerate")) {
+          gst_structure_get_fraction (structure, "framerate", &config.rate_n,
+              &config.rate_d);
+        } else {
+          /* cannot get the framerate */
+          config.rate_n = 0;
+          config.rate_d = 1;
+        }
+        break;
+      } else {
+        const gchar *struct_name = gst_structure_get_name (structure);
 
-      if (struct_name != NULL) {
-        const NNStreamerExternalConverter *ex;
+        if (struct_name != NULL) {
+          const NNStreamerExternalConverter *ex;
 
-        ex = findExternalConverter (struct_name);
+          ex = findExternalConverter (struct_name);
 
-        if (ex != NULL && self->externalConverter == NULL) {
-          in_type = _NNS_MEDIA_PLUGINS;
-          self->externalConverter = ex;
+          if (ex != NULL && self->externalConverter == NULL) {
+            in_type = _NNS_MEDIA_ANY;
+            self->externalConverter = ex;
 
-          if (NULL == ex->get_out_config || !ex->get_out_config (caps, &config)) {
-            GST_ERROR_OBJECT (self,
-                "Failed to get tensors info from %s. Check the given options.",
-                struct_name);
-            ml_loge ("Please set the options property correctly.\n");
-            self->externalConverter = NULL;
-            return FALSE;
+            if (NULL == ex->get_out_config
+                || !ex->get_out_config (caps, &config)) {
+              GST_ERROR_OBJECT (self,
+                  "Failed to get tensors info from %s. Check the given options.",
+                  struct_name);
+              ml_loge ("Please set the options property correctly.\n");
+              self->externalConverter = NULL;
+              return FALSE;
+            }
+            break;
           }
-          break;
         }
+        GST_ERROR_OBJECT (self, "Unsupported type %d\n", in_type);
+        return FALSE;
       }
-      GST_ERROR_OBJECT (self, "Unsupported type %d\n", in_type);
-      return FALSE;
     }
   }
 
@@ -1865,3 +1973,48 @@ nnstreamer_converter_set_custom_property_desc (const char *name,
       varargs);
   va_end (varargs);
 }
+
+/**
+ * @brief Registers a callback for tensor_converter custom condition
+ * @return 0 if success. -ERRNO if error.
+ */
+int
+nnstreamer_converter_custom_register (const gchar * name,
+    tensor_converter_custom func, void *data)
+{
+  converter_custom_cb_s *ptr;
+
+  g_return_val_if_fail (name && strlen (name), -EINVAL);
+  g_return_val_if_fail (func, -EINVAL);
+
+  if (!(ptr = g_try_new0 (converter_custom_cb_s, 1)))
+    return -ENOMEM;
+
+  ptr->func = func;
+  ptr->data = data;
+
+  if (register_subplugin (NNS_CUSTOM_CONVERTER, name, ptr) == TRUE)
+    return 0;
+
+  g_free (ptr);
+  return -EINVAL;
+}
+
+/**
+ * @brief Unregisters a callback for tensor_converter custom condition
+ * @return 0 if success. -ERRNO if error.
+ */
+int
+nnstreamer_converter_custom_unregister (const gchar * name)
+{
+  converter_custom_cb_s *ptr;
+
+  ptr = (converter_custom_cb_s *) get_subplugin (NNS_CUSTOM_CONVERTER, name);
+  if (!unregister_subplugin (NNS_CUSTOM_CONVERTER, name)) {
+    ml_loge ("Failed to unregister custom callback %s.", name);
+    return -EINVAL;
+  }
+  g_free (ptr);
+
+  return 0;
+}
index 3d70ca5..4338573 100644 (file)
@@ -38,6 +38,7 @@
 #include <gst/base/gstadapter.h>
 #include <tensor_common.h>
 #include "nnstreamer_plugin_api_converter.h"
+#include "tensor_converter_custom.h"
 
 G_BEGIN_DECLS
 
@@ -54,6 +55,19 @@ G_BEGIN_DECLS
 
 typedef struct _GstTensorConverter GstTensorConverter;
 typedef struct _GstTensorConverterClass GstTensorConverterClass;
+typedef struct
+{
+  tensor_converter_custom func;
+  void * data;
+} converter_custom_cb_s;
+
+/**
+ * @brief tensor converter mode
+ */
+typedef enum {
+  _CONVERTER_MODE_NONE = 0,    /**< Normal mode (default) */
+  _CONVERTER_MODE_CUSTOM = 1,  /**<  Custom mode */
+} tensor_converter_mode;
 
 /**
  * @brief Internal data structure for tensor_converter instances.
@@ -85,6 +99,10 @@ struct _GstTensorConverter
   gboolean need_segment; /**< True to handle seg event */
   GstSegment segment; /**< Segment, supposed time format */
   GstClockTime old_timestamp; /**< timestamp at prev buffer */
+
+  tensor_converter_mode mode; /**< tensor converter operating mode */
+  gchar *mode_option; /**< tensor converter mode option */
+  converter_custom_cb_s custom;
 };
 
 /**
index 529c20d..3334151 100644 (file)
@@ -859,6 +859,7 @@ cp -r result %{buildroot}%{_datadir}/nnstreamer/unittest/
 %{_includedir}/nnstreamer/tensor_typedef.h
 %{_includedir}/nnstreamer/tensor_filter_custom.h
 %{_includedir}/nnstreamer/tensor_filter_custom_easy.h
+%{_includedir}/nnstreamer/tensor_converter_custom.h
 %{_includedir}/nnstreamer/nnstreamer_plugin_api_filter.h
 %{_includedir}/nnstreamer/nnstreamer_plugin_api_decoder.h
 %{_includedir}/nnstreamer/nnstreamer_plugin_api_converter.h