v4l2videoenc: fix type conversion errors
[platform/upstream/gst-plugins-good.git] / sys / v4l2 / gstv4l2videoenc.c
index 12f1eb4..7c17c74 100644 (file)
@@ -64,13 +64,11 @@ gst_v4l2_video_enc_set_property (GObject * object,
   GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (object);
 
   switch (prop_id) {
-    case PROP_OUTPUT_IO_MODE:
-      gst_v4l2_object_set_property_helper (self->v4l2output,
-          prop_id, value, pspec);
-      break;
     case PROP_CAPTURE_IO_MODE:
-      gst_v4l2_object_set_property_helper (self->v4l2capture,
-          prop_id, value, pspec);
+      if (!gst_v4l2_object_set_property_helper (self->v4l2capture,
+              prop_id, value, pspec)) {
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      }
       break;
 
       /* By default, only set on output */
@@ -90,13 +88,11 @@ gst_v4l2_video_enc_get_property (GObject * object,
   GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (object);
 
   switch (prop_id) {
-    case PROP_OUTPUT_IO_MODE:
-      gst_v4l2_object_get_property_helper (self->v4l2output,
-          prop_id, value, pspec);
-      break;
     case PROP_CAPTURE_IO_MODE:
-      gst_v4l2_object_get_property_helper (self->v4l2output,
-          PROP_IO_MODE, value, pspec);
+      if (!gst_v4l2_object_get_property_helper (self->v4l2capture,
+              prop_id, value, pspec)) {
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      }
       break;
 
       /* By default read from output */
@@ -241,7 +237,7 @@ gst_v4l2_encoder_cmd (GstV4l2Object * v4l2object, guint cmd, guint flags)
 
   ecmd.cmd = cmd;
   ecmd.flags = flags;
-  if (v4l2_ioctl (v4l2object->video_fd, VIDIOC_ENCODER_CMD, &ecmd) < 0)
+  if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_ENCODER_CMD, &ecmd) < 0)
     goto ecmd_failed;
 
   return TRUE;
@@ -278,6 +274,7 @@ gst_v4l2_video_enc_finish (GstVideoEncoder * encoder)
     GstTask *task = encoder->srcpad->task;
 
     /* Wait for the task to be drained */
+    GST_DEBUG_OBJECT (self, "Waiting for encoder stop");
     GST_OBJECT_LOCK (task);
     while (GST_TASK_STATE (task) == GST_TASK_STARTED)
       GST_TASK_WAIT (task);
@@ -321,6 +318,9 @@ gst_v4l2_video_enc_set_format (GstVideoEncoder * encoder,
     if (gst_v4l2_video_enc_finish (encoder) != GST_FLOW_OK)
       return FALSE;
 
+    gst_v4l2_object_stop (self->v4l2output);
+    gst_v4l2_object_stop (self->v4l2capture);
+
     gst_video_codec_state_unref (self->input_state);
     self->input_state = NULL;
   }
@@ -373,21 +373,229 @@ gst_v4l2_video_enc_flush (GstVideoEncoder * encoder)
   return TRUE;
 }
 
+struct ProfileLevelCtx
+{
+  GstV4l2VideoEnc *self;
+  const gchar *profile;
+  const gchar *level;
+};
+
+static gboolean
+get_string_list (GstStructure * s, const gchar * field, GQueue * queue)
+{
+  const GValue *value;
+
+  value = gst_structure_get_value (s, field);
+
+  if (!value)
+    return FALSE;
+
+  if (GST_VALUE_HOLDS_LIST (value)) {
+    guint i;
+
+    if (gst_value_list_get_size (value) == 0)
+      return FALSE;
+
+    for (i = 0; i < gst_value_list_get_size (value); i++) {
+      const GValue *item = gst_value_list_get_value (value, i);
+
+      if (G_VALUE_HOLDS_STRING (item))
+        g_queue_push_tail (queue, g_value_dup_string (item));
+    }
+  } else if (G_VALUE_HOLDS_STRING (value)) {
+    g_queue_push_tail (queue, g_value_dup_string (value));
+  }
+
+  return TRUE;
+}
+
+static gboolean
+negotiate_profile_and_level (GstCapsFeatures * features, GstStructure * s,
+    gpointer user_data)
+{
+  struct ProfileLevelCtx *ctx = user_data;
+  GstV4l2VideoEncClass *klass = GST_V4L2_VIDEO_ENC_GET_CLASS (ctx->self);
+  GstV4l2Object *v4l2object = GST_V4L2_VIDEO_ENC (ctx->self)->v4l2output;
+  GQueue profiles = G_QUEUE_INIT;
+  GQueue levels = G_QUEUE_INIT;
+  gboolean failed = FALSE;
+
+  if (klass->profile_cid && get_string_list (s, "profile", &profiles)) {
+    GList *l;
+
+    for (l = profiles.head; l; l = l->next) {
+      struct v4l2_control control = { 0, };
+      gint v4l2_profile;
+      const gchar *profile = l->data;
+
+      GST_TRACE_OBJECT (ctx->self, "Trying profile %s", profile);
+
+      control.id = klass->profile_cid;
+      control.value = v4l2_profile = klass->profile_from_string (profile);
+
+      if (control.value < 0)
+        continue;
+
+      if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
+        GST_WARNING_OBJECT (ctx->self, "Failed to set %s profile: '%s'",
+            klass->codec_name, g_strerror (errno));
+        break;
+      }
+
+      profile = klass->profile_to_string (control.value);
+
+      if (control.value == v4l2_profile) {
+        ctx->profile = profile;
+        break;
+      }
+
+      if (g_list_find_custom (l, profile, g_str_equal)) {
+        ctx->profile = profile;
+        break;
+      }
+    }
+
+    if (profiles.length && !ctx->profile)
+      failed = TRUE;
+
+    g_queue_foreach (&profiles, (GFunc) g_free, NULL);
+    g_queue_clear (&profiles);
+  }
+
+  if (!failed && klass->level_cid && get_string_list (s, "level", &levels)) {
+    GList *l;
+
+    for (l = levels.head; l; l = l->next) {
+      struct v4l2_control control = { 0, };
+      gint v4l2_level;
+      const gchar *level = l->data;
+
+      GST_TRACE_OBJECT (ctx->self, "Trying level %s", level);
+
+      control.id = klass->level_cid;
+      control.value = v4l2_level = klass->level_from_string (level);
+
+      if (control.value < 0)
+        continue;
+
+      if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
+        GST_WARNING_OBJECT (ctx->self, "Failed to set %s level: '%s'",
+            klass->codec_name, g_strerror (errno));
+        break;
+      }
+
+      level = klass->level_to_string (control.value);
+
+      if (control.value == v4l2_level) {
+        ctx->level = level;
+        break;
+      }
+
+      if (g_list_find_custom (l, level, g_str_equal)) {
+        ctx->level = level;
+        break;
+      }
+    }
+
+    if (levels.length && !ctx->level)
+      failed = TRUE;
+
+    g_queue_foreach (&levels, (GFunc) g_free, NULL);
+    g_queue_clear (&levels);
+  }
+
+  /* If it failed, we continue */
+  return failed;
+}
+
 static gboolean
 gst_v4l2_video_enc_negotiate (GstVideoEncoder * encoder)
 {
+  GstV4l2VideoEncClass *klass = GST_V4L2_VIDEO_ENC_GET_CLASS (encoder);
   GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (encoder);
-  gboolean ret;
+  GstV4l2Object *v4l2object = self->v4l2output;
+  GstCaps *allowed_caps;
+  struct ProfileLevelCtx ctx = { self, NULL, NULL };
+  GstVideoCodecState *state;
+  GstStructure *s;
 
-  ret = GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder);
+  GST_DEBUG_OBJECT (self, "Negotiating %s profile and level.",
+      klass->codec_name);
 
-  if (!gst_buffer_pool_set_active (GST_BUFFER_POOL (self->v4l2capture->pool),
-          TRUE)) {
-    GST_WARNING_OBJECT (self, "Could not activate capture buffer pool.");
-    ret = FALSE;
+  /* Only renegotiate on upstream changes */
+  if (self->input_state)
+    return TRUE;
+
+  allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
+
+  if (allowed_caps) {
+
+    if (gst_caps_is_empty (allowed_caps))
+      goto not_negotiated;
+
+    allowed_caps = gst_caps_make_writable (allowed_caps);
+
+    /* negotiate_profile_and_level() will return TRUE on failure to keep
+     * iterating, if gst_caps_foreach() returns TRUE it means there was no
+     * compatible profile and level in any of the structure */
+    if (gst_caps_foreach (allowed_caps, negotiate_profile_and_level, &ctx)) {
+      goto no_profile_level;
+    }
   }
 
-  return ret;
+  if (klass->profile_cid && !ctx.profile) {
+    struct v4l2_control control = { 0, };
+
+    control.id = klass->profile_cid;
+
+    if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
+      goto g_ctrl_failed;
+
+    ctx.profile = klass->profile_to_string (control.value);
+  }
+
+  if (klass->level_cid && !ctx.level) {
+    struct v4l2_control control = { 0, };
+
+    control.id = klass->level_cid;
+
+    if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
+      goto g_ctrl_failed;
+
+    ctx.level = klass->level_to_string (control.value);
+  }
+
+  GST_DEBUG_OBJECT (self, "Selected %s profile %s at level %s",
+      klass->codec_name, ctx.profile, ctx.level);
+
+  state = gst_video_encoder_get_output_state (encoder);
+  s = gst_caps_get_structure (state->caps, 0);
+
+  if (klass->profile_cid)
+    gst_structure_set (s, "profile", G_TYPE_STRING, ctx.profile, NULL);
+
+  if (klass->level_cid)
+    gst_structure_set (s, "level", G_TYPE_STRING, ctx.level, NULL);
+
+  if (!GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder))
+    return FALSE;
+
+  return TRUE;
+
+g_ctrl_failed:
+  GST_WARNING_OBJECT (self, "Failed to get %s profile and level: '%s'",
+      klass->codec_name, g_strerror (errno));
+  goto not_negotiated;
+
+no_profile_level:
+  GST_WARNING_OBJECT (self, "No compatible level and profile in caps: %"
+      GST_PTR_FORMAT, allowed_caps);
+  goto not_negotiated;
+
+not_negotiated:
+  if (allowed_caps)
+    gst_caps_unref (allowed_caps);
+  return FALSE;
 }
 
 static GstVideoCodecFrame *
@@ -453,6 +661,15 @@ gst_v4l2_video_enc_loop (GstVideoEncoder * encoder)
   frame = gst_v4l2_video_enc_get_oldest_frame (encoder);
 
   if (frame) {
+    /* At this point, the delta unit buffer flag is already correctly set by
+     * gst_v4l2_buffer_pool_process. Since gst_video_encoder_finish_frame
+     * will overwrite it from GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame),
+     * set that here.
+     */
+    if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT))
+      GST_VIDEO_CODEC_FRAME_UNSET_SYNC_POINT (frame);
+    else
+      GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
     frame->output_buffer = buffer;
     buffer = NULL;
     ret = gst_video_encoder_finish_frame (encoder, frame);
@@ -496,14 +713,15 @@ gst_v4l2_video_enc_handle_frame (GstVideoEncoder * encoder,
 {
   GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (encoder);
   GstFlowReturn ret = GST_FLOW_OK;
+  GstTaskState task_state;
 
   GST_DEBUG_OBJECT (self, "Handling frame %d", frame->system_frame_number);
 
   if (G_UNLIKELY (!g_atomic_int_get (&self->active)))
     goto flushing;
 
-  if (gst_pad_get_task_state (GST_VIDEO_DECODER_SRC_PAD (self)) ==
-      GST_TASK_STOPPED) {
+  task_state = gst_pad_get_task_state (GST_VIDEO_ENCODER_SRC_PAD (self));
+  if (task_state == GST_TASK_STOPPED || task_state == GST_TASK_PAUSED) {
     GstBufferPool *pool = GST_BUFFER_POOL (self->v4l2output->pool);
 
     /* It possible that the processing thread stopped due to an error */
@@ -530,6 +748,12 @@ gst_v4l2_video_enc_handle_frame (GstVideoEncoder * encoder,
         goto activate_failed;
     }
 
+    if (!gst_buffer_pool_set_active
+        (GST_BUFFER_POOL (self->v4l2capture->pool), TRUE)) {
+      GST_WARNING_OBJECT (self, "Could not activate capture buffer pool.");
+      goto activate_failed;
+    }
+
     GST_DEBUG_OBJECT (self, "Starting encoding thread");
 
     /* Start the processing task, when it quits, the task will disable input
@@ -603,6 +827,7 @@ gst_v4l2_video_enc_decide_allocation (GstVideoEncoder *
 {
   GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (encoder);
   GstVideoCodecState *state = gst_video_encoder_get_output_state (encoder);
+  GstCaps *caps;
   GstV4l2Error error = GST_V4L2_ERROR_INIT;
   GstClockTime latency;
   gboolean ret = FALSE;
@@ -611,11 +836,15 @@ gst_v4l2_video_enc_decide_allocation (GstVideoEncoder *
    * GstVideoEncoder have set the width, height and framerate into the state
    * caps. These are needed by the driver to calculate the buffer size and to
    * implement bitrate adaptation. */
-  if (!gst_v4l2_object_set_format (self->v4l2capture, state->caps, &error)) {
+  caps = gst_caps_copy (state->caps);
+  gst_structure_remove_field (gst_caps_get_structure (caps, 0), "colorimetry");
+  if (!gst_v4l2_object_set_format (self->v4l2capture, caps, &error)) {
     gst_v4l2_error (self, &error);
+    gst_caps_unref (caps);
     ret = FALSE;
     goto done;
   }
+  gst_caps_unref (caps);
 
   if (gst_v4l2_object_decide_allocation (self->v4l2capture, query)) {
     GstVideoEncoderClass *enc_class = GST_VIDEO_ENCODER_CLASS (parent_class);
@@ -744,8 +973,9 @@ gst_v4l2_video_enc_sink_event (GstVideoEncoder * encoder, GstEvent * event)
 {
   GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (encoder);
   gboolean ret;
+  GstEventType type = GST_EVENT_TYPE (event);
 
-  switch (GST_EVENT_TYPE (event)) {
+  switch (type) {
     case GST_EVENT_FLUSH_START:
       GST_DEBUG_OBJECT (self, "flush start");
       gst_v4l2_object_unlock (self->v4l2output);
@@ -757,7 +987,7 @@ gst_v4l2_video_enc_sink_event (GstVideoEncoder * encoder, GstEvent * event)
 
   ret = GST_VIDEO_ENCODER_CLASS (parent_class)->sink_event (encoder, event);
 
-  switch (GST_EVENT_TYPE (event)) {
+  switch (type) {
     case GST_EVENT_FLUSH_START:
       gst_pad_stop_task (encoder->srcpad);
       GST_DEBUG_OBJECT (self, "flush start done");
@@ -783,6 +1013,7 @@ gst_v4l2_video_enc_change_state (GstElement * element,
   return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
 }
 
+
 static void
 gst_v4l2_video_enc_dispose (GObject * object)
 {
@@ -805,6 +1036,7 @@ gst_v4l2_video_enc_finalize (GObject * object)
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
+
 static void
 gst_v4l2_video_enc_init (GstV4l2VideoEnc * self)
 {
@@ -818,16 +1050,16 @@ gst_v4l2_video_enc_subinstance_init (GTypeInstance * instance, gpointer g_class)
   GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (instance);
 
   self->v4l2output = gst_v4l2_object_new (GST_ELEMENT (self),
+      GST_OBJECT (GST_VIDEO_ENCODER_SINK_PAD (self)),
       V4L2_BUF_TYPE_VIDEO_OUTPUT, klass->default_device,
       gst_v4l2_get_output, gst_v4l2_set_output, NULL);
   self->v4l2output->no_initial_format = TRUE;
   self->v4l2output->keep_aspect = FALSE;
 
   self->v4l2capture = gst_v4l2_object_new (GST_ELEMENT (self),
+      GST_OBJECT (GST_VIDEO_ENCODER_SRC_PAD (self)),
       V4L2_BUF_TYPE_VIDEO_CAPTURE, klass->default_device,
       gst_v4l2_get_input, gst_v4l2_set_input, NULL);
-  self->v4l2capture->no_initial_format = TRUE;
-  self->v4l2output->keep_aspect = FALSE;
 }
 
 static void
@@ -899,11 +1131,34 @@ gst_v4l2_video_enc_subclass_init (gpointer g_class, gpointer data)
       gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
           cdata->src_caps));
 
+  gst_caps_unref (cdata->sink_caps);
+  gst_caps_unref (cdata->src_caps);
   g_free (cdata);
 }
 
 /* Probing functions */
 gboolean
+gst_v4l2_is_video_enc (GstCaps * sink_caps, GstCaps * src_caps,
+    GstCaps * codec_caps)
+{
+  gboolean ret = FALSE;
+  gboolean (*check_caps) (const GstCaps *, const GstCaps *);
+
+  if (codec_caps) {
+    check_caps = gst_caps_can_intersect;
+  } else {
+    codec_caps = gst_v4l2_object_get_codec_caps ();
+    check_caps = gst_caps_is_subset;
+  }
+
+  if (gst_caps_is_subset (sink_caps, gst_v4l2_object_get_raw_caps ())
+      && check_caps (src_caps, codec_caps))
+    ret = TRUE;
+
+  return ret;
+}
+
+void
 gst_v4l2_video_enc_register (GstPlugin * plugin, GType type,
     const char *codec, const gchar * basename, const gchar * device_path,
     GstCaps * sink_caps, GstCaps * codec_caps, GstCaps * src_caps)
@@ -942,9 +1197,9 @@ gst_v4l2_video_enc_register (GstPlugin * plugin, GType type,
   }
 
   subtype = g_type_register_static (type, type_name, &type_info, 0);
-  gst_element_register (plugin, type_name, GST_RANK_PRIMARY + 1, subtype);
 
-  g_free (type_name);
+  if (!gst_element_register (plugin, type_name, GST_RANK_PRIMARY + 1, subtype))
+    GST_WARNING ("Failed to register plugin '%s'", type_name);
 
-  return TRUE;
+  g_free (type_name);
 }