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 */
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 */
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;
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);
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;
}
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 *
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);
{
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 */
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
{
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;
* 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);
{
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);
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");
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
}
+
static void
gst_v4l2_video_enc_dispose (GObject * object)
{
G_OBJECT_CLASS (parent_class)->finalize (object);
}
+
static void
gst_v4l2_video_enc_init (GstV4l2VideoEnc * self)
{
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
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)
}
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);
}