| /* |
| * Copyright (C) 2014 Collabora Ltd. |
| * Author: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <string.h> |
| |
| #include "gstv4l2transform.h" |
| #include "v4l2_calls.h" |
| |
| #include <string.h> |
| #include <gst/gst-i18n-plugin.h> |
| |
| #define DEFAULT_PROP_DEVICE "/dev/video10" |
| |
| #define V4L2_TRANSFORM_QUARK \ |
| g_quark_from_static_string("gst-v4l2-transform-info") |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_v4l2_transform_debug); |
| #define GST_CAT_DEFAULT gst_v4l2_transform_debug |
| |
| |
| enum |
| { |
| PROP_0, |
| V4L2_STD_OBJECT_PROPS |
| }; |
| |
| typedef struct |
| { |
| gchar *device; |
| GstCaps *sink_caps; |
| GstCaps *src_caps; |
| } GstV4l2TransformCData; |
| |
| #define gst_v4l2_transform_parent_class parent_class |
| G_DEFINE_ABSTRACT_TYPE (GstV4l2Transform, gst_v4l2_transform, |
| GST_TYPE_BASE_TRANSFORM); |
| |
| static void |
| gst_v4l2_transform_set_property (GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (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); |
| break; |
| |
| /* By default, only set on output */ |
| default: |
| if (!gst_v4l2_object_set_property_helper (self->v4l2output, |
| prop_id, value, pspec)) { |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| } |
| break; |
| } |
| } |
| |
| static void |
| gst_v4l2_transform_get_property (GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (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->v4l2capture, prop_id, value, |
| pspec); |
| break; |
| |
| /* By default read from output */ |
| default: |
| if (!gst_v4l2_object_get_property_helper (self->v4l2output, |
| prop_id, value, pspec)) { |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| } |
| break; |
| } |
| } |
| |
| static gboolean |
| gst_v4l2_transform_open (GstV4l2Transform * self) |
| { |
| GST_DEBUG_OBJECT (self, "Opening"); |
| |
| if (!gst_v4l2_object_open (self->v4l2output)) |
| goto failure; |
| |
| if (!gst_v4l2_object_open_shared (self->v4l2capture, self->v4l2output)) |
| goto failure; |
| |
| self->probed_sinkcaps = gst_v4l2_object_get_caps (self->v4l2output, |
| gst_v4l2_object_get_raw_caps ()); |
| |
| if (gst_caps_is_empty (self->probed_sinkcaps)) |
| goto no_input_format; |
| |
| self->probed_srccaps = gst_v4l2_object_get_caps (self->v4l2capture, |
| gst_v4l2_object_get_raw_caps ()); |
| |
| if (gst_caps_is_empty (self->probed_srccaps)) |
| goto no_output_format; |
| |
| return TRUE; |
| |
| no_input_format: |
| GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, |
| (_("Converter on device %s has no supported input format"), |
| self->v4l2output->videodev), (NULL)); |
| goto failure; |
| |
| |
| no_output_format: |
| GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, |
| (_("Converter on device %s has no supported output format"), |
| self->v4l2output->videodev), (NULL)); |
| goto failure; |
| |
| failure: |
| if (GST_V4L2_IS_OPEN (self->v4l2output)) |
| gst_v4l2_object_close (self->v4l2output); |
| |
| if (GST_V4L2_IS_OPEN (self->v4l2capture)) |
| gst_v4l2_object_close (self->v4l2capture); |
| |
| gst_caps_replace (&self->probed_srccaps, NULL); |
| gst_caps_replace (&self->probed_sinkcaps, NULL); |
| |
| return FALSE; |
| } |
| |
| static void |
| gst_v4l2_transform_close (GstV4l2Transform * self) |
| { |
| GST_DEBUG_OBJECT (self, "Closing"); |
| |
| gst_v4l2_object_close (self->v4l2output); |
| gst_v4l2_object_close (self->v4l2capture); |
| |
| gst_caps_replace (&self->probed_srccaps, NULL); |
| gst_caps_replace (&self->probed_sinkcaps, NULL); |
| } |
| |
| static gboolean |
| gst_v4l2_transform_stop (GstBaseTransform * trans) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans); |
| |
| GST_DEBUG_OBJECT (self, "Stop"); |
| |
| gst_v4l2_object_stop (self->v4l2output); |
| gst_v4l2_object_stop (self->v4l2capture); |
| gst_caps_replace (&self->incaps, NULL); |
| gst_caps_replace (&self->outcaps, NULL); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_v4l2_transform_set_caps (GstBaseTransform * trans, GstCaps * incaps, |
| GstCaps * outcaps) |
| { |
| GstV4l2Error error = GST_V4L2_ERROR_INIT; |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans); |
| |
| if (self->incaps && self->outcaps) { |
| if (gst_caps_is_equal (incaps, self->incaps) && |
| gst_caps_is_equal (outcaps, self->outcaps)) { |
| GST_DEBUG_OBJECT (trans, "Caps did not changed"); |
| return TRUE; |
| } |
| } |
| |
| /* TODO Add renegotiation support */ |
| g_return_val_if_fail (!GST_V4L2_IS_ACTIVE (self->v4l2output), FALSE); |
| g_return_val_if_fail (!GST_V4L2_IS_ACTIVE (self->v4l2capture), FALSE); |
| |
| gst_caps_replace (&self->incaps, incaps); |
| gst_caps_replace (&self->outcaps, outcaps); |
| |
| if (!gst_v4l2_object_set_format (self->v4l2output, incaps, &error)) |
| goto incaps_failed; |
| |
| if (!gst_v4l2_object_set_format (self->v4l2capture, outcaps, &error)) |
| goto outcaps_failed; |
| |
| /* FIXME implement fallback if crop not supported */ |
| if (!gst_v4l2_object_set_crop (self->v4l2output)) |
| goto failed; |
| |
| if (!gst_v4l2_object_set_crop (self->v4l2capture)) |
| goto failed; |
| |
| return TRUE; |
| |
| incaps_failed: |
| { |
| GST_ERROR_OBJECT (self, "failed to set input caps: %" GST_PTR_FORMAT, |
| incaps); |
| gst_v4l2_error (self, &error); |
| goto failed; |
| } |
| outcaps_failed: |
| { |
| gst_v4l2_object_stop (self->v4l2output); |
| GST_ERROR_OBJECT (self, "failed to set output caps: %" GST_PTR_FORMAT, |
| outcaps); |
| gst_v4l2_error (self, &error); |
| goto failed; |
| } |
| failed: |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_v4l2_transform_query (GstBaseTransform * trans, GstPadDirection direction, |
| GstQuery * query) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans); |
| gboolean ret = TRUE; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_CAPS:{ |
| GstCaps *filter, *caps = NULL, *result = NULL; |
| GstPad *pad, *otherpad; |
| |
| gst_query_parse_caps (query, &filter); |
| |
| if (direction == GST_PAD_SRC) { |
| pad = GST_BASE_TRANSFORM_SRC_PAD (trans); |
| otherpad = GST_BASE_TRANSFORM_SINK_PAD (trans); |
| if (self->probed_srccaps) |
| caps = gst_caps_ref (self->probed_srccaps); |
| } else { |
| pad = GST_BASE_TRANSFORM_SINK_PAD (trans); |
| otherpad = GST_BASE_TRANSFORM_SRC_PAD (trans); |
| if (self->probed_sinkcaps) |
| caps = gst_caps_ref (self->probed_sinkcaps); |
| } |
| |
| if (!caps) |
| caps = gst_pad_get_pad_template_caps (pad); |
| |
| if (filter) { |
| GstCaps *tmp = caps; |
| caps = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (tmp); |
| } |
| |
| result = gst_pad_peer_query_caps (otherpad, caps); |
| result = gst_caps_make_writable (result); |
| gst_caps_append (result, caps); |
| |
| GST_DEBUG_OBJECT (self, "Returning %s caps %" GST_PTR_FORMAT, |
| GST_PAD_NAME (pad), result); |
| |
| gst_query_set_caps_result (query, result); |
| gst_caps_unref (result); |
| break; |
| } |
| |
| default: |
| ret = GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction, |
| query); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_v4l2_transform_decide_allocation (GstBaseTransform * trans, |
| GstQuery * query) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans); |
| gboolean ret = FALSE; |
| |
| GST_DEBUG_OBJECT (self, "called"); |
| |
| if (gst_v4l2_object_decide_allocation (self->v4l2capture, query)) { |
| GstBufferPool *pool = GST_BUFFER_POOL (self->v4l2capture->pool); |
| |
| ret = GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans, |
| query); |
| |
| if (!gst_buffer_pool_set_active (pool, TRUE)) |
| goto activate_failed; |
| } |
| |
| return ret; |
| |
| activate_failed: |
| GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, |
| ("failed to activate bufferpool"), ("failed to activate bufferpool")); |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_v4l2_transform_propose_allocation (GstBaseTransform * trans, |
| GstQuery * decide_query, GstQuery * query) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans); |
| gboolean ret = FALSE; |
| |
| GST_DEBUG_OBJECT (self, "called"); |
| |
| if (decide_query == NULL) |
| ret = TRUE; |
| else |
| ret = gst_v4l2_object_propose_allocation (self->v4l2output, query); |
| |
| if (ret) |
| ret = GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans, |
| decide_query, query); |
| |
| return ret; |
| } |
| |
| /* copies the given caps */ |
| static GstCaps * |
| gst_v4l2_transform_caps_remove_format_info (GstCaps * caps) |
| { |
| GstStructure *st; |
| GstCapsFeatures *f; |
| gint i, n; |
| GstCaps *res; |
| |
| res = gst_caps_new_empty (); |
| |
| n = gst_caps_get_size (caps); |
| for (i = 0; i < n; i++) { |
| st = gst_caps_get_structure (caps, i); |
| f = gst_caps_get_features (caps, i); |
| |
| /* If this is already expressed by the existing caps |
| * skip this structure */ |
| if (i > 0 && gst_caps_is_subset_structure_full (res, st, f)) |
| continue; |
| |
| st = gst_structure_copy (st); |
| /* Only remove format info for the cases when we can actually convert */ |
| if (!gst_caps_features_is_any (f) |
| && gst_caps_features_is_equal (f, |
| GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY)) |
| gst_structure_remove_fields (st, "format", "colorimetry", "chroma-site", |
| "width", "height", "pixel-aspect-ratio", NULL); |
| |
| gst_caps_append_structure_full (res, st, gst_caps_features_copy (f)); |
| } |
| |
| return res; |
| } |
| |
| /* The caps can be transformed into any other caps with format info removed. |
| * However, we should prefer passthrough, so if passthrough is possible, |
| * put it first in the list. */ |
| static GstCaps * |
| gst_v4l2_transform_transform_caps (GstBaseTransform * btrans, |
| GstPadDirection direction, GstCaps * caps, GstCaps * filter) |
| { |
| GstCaps *tmp, *tmp2; |
| GstCaps *result; |
| |
| /* Get all possible caps that we can transform to */ |
| tmp = gst_v4l2_transform_caps_remove_format_info (caps); |
| |
| if (filter) { |
| tmp2 = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (tmp); |
| tmp = tmp2; |
| } |
| |
| result = tmp; |
| |
| GST_DEBUG_OBJECT (btrans, "transformed %" GST_PTR_FORMAT " into %" |
| GST_PTR_FORMAT, caps, result); |
| |
| return result; |
| } |
| |
| static GstCaps * |
| gst_v4l2_transform_fixate_caps (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * caps, GstCaps * othercaps) |
| { |
| GstStructure *ins, *outs; |
| const GValue *from_par, *to_par; |
| GValue fpar = { 0, }, tpar = { |
| 0,}; |
| |
| othercaps = gst_caps_truncate (othercaps); |
| othercaps = gst_caps_make_writable (othercaps); |
| |
| GST_DEBUG_OBJECT (trans, "trying to fixate othercaps %" GST_PTR_FORMAT |
| " based on caps %" GST_PTR_FORMAT, othercaps, caps); |
| |
| ins = gst_caps_get_structure (caps, 0); |
| outs = gst_caps_get_structure (othercaps, 0); |
| |
| { |
| const gchar *in_format; |
| |
| in_format = gst_structure_get_string (ins, "format"); |
| if (in_format) { |
| /* Try to set output format for pass through */ |
| gst_structure_fixate_field_string (outs, "format", in_format); |
| } |
| |
| } |
| |
| from_par = gst_structure_get_value (ins, "pixel-aspect-ratio"); |
| to_par = gst_structure_get_value (outs, "pixel-aspect-ratio"); |
| |
| /* If we're fixating from the sinkpad we always set the PAR and |
| * assume that missing PAR on the sinkpad means 1/1 and |
| * missing PAR on the srcpad means undefined |
| */ |
| if (direction == GST_PAD_SINK) { |
| if (!from_par) { |
| g_value_init (&fpar, GST_TYPE_FRACTION); |
| gst_value_set_fraction (&fpar, 1, 1); |
| from_par = &fpar; |
| } |
| if (!to_par) { |
| g_value_init (&tpar, GST_TYPE_FRACTION_RANGE); |
| gst_value_set_fraction_range_full (&tpar, 1, G_MAXINT, G_MAXINT, 1); |
| to_par = &tpar; |
| } |
| } else { |
| if (!to_par) { |
| g_value_init (&tpar, GST_TYPE_FRACTION); |
| gst_value_set_fraction (&tpar, 1, 1); |
| to_par = &tpar; |
| |
| gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, |
| NULL); |
| } |
| if (!from_par) { |
| g_value_init (&fpar, GST_TYPE_FRACTION); |
| gst_value_set_fraction (&fpar, 1, 1); |
| from_par = &fpar; |
| } |
| } |
| |
| /* we have both PAR but they might not be fixated */ |
| { |
| gint from_w, from_h, from_par_n, from_par_d, to_par_n, to_par_d; |
| gint w = 0, h = 0; |
| gint from_dar_n, from_dar_d; |
| gint num, den; |
| |
| /* from_par should be fixed */ |
| g_return_val_if_fail (gst_value_is_fixed (from_par), othercaps); |
| |
| from_par_n = gst_value_get_fraction_numerator (from_par); |
| from_par_d = gst_value_get_fraction_denominator (from_par); |
| |
| gst_structure_get_int (ins, "width", &from_w); |
| gst_structure_get_int (ins, "height", &from_h); |
| |
| gst_structure_get_int (outs, "width", &w); |
| gst_structure_get_int (outs, "height", &h); |
| |
| /* if both width and height are already fixed, we can't do anything |
| * about it anymore */ |
| if (w && h) { |
| guint n, d; |
| |
| GST_DEBUG_OBJECT (trans, "dimensions already set to %dx%d, not fixating", |
| w, h); |
| if (!gst_value_is_fixed (to_par)) { |
| if (gst_video_calculate_display_ratio (&n, &d, from_w, from_h, |
| from_par_n, from_par_d, w, h)) { |
| GST_DEBUG_OBJECT (trans, "fixating to_par to %dx%d", n, d); |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio")) |
| gst_structure_fixate_field_nearest_fraction (outs, |
| "pixel-aspect-ratio", n, d); |
| else if (n != d) |
| gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, |
| n, d, NULL); |
| } |
| } |
| goto done; |
| } |
| |
| /* Calculate input DAR */ |
| if (!gst_util_fraction_multiply (from_w, from_h, from_par_n, from_par_d, |
| &from_dar_n, &from_dar_d)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| goto done; |
| } |
| |
| GST_DEBUG_OBJECT (trans, "Input DAR is %d/%d", from_dar_n, from_dar_d); |
| |
| /* If either width or height are fixed there's not much we |
| * can do either except choosing a height or width and PAR |
| * that matches the DAR as good as possible |
| */ |
| if (h) { |
| GstStructure *tmp; |
| gint set_w, set_par_n, set_par_d; |
| |
| GST_DEBUG_OBJECT (trans, "height is fixed (%d)", h); |
| |
| /* If the PAR is fixed too, there's not much to do |
| * except choosing the width that is nearest to the |
| * width with the same DAR */ |
| if (gst_value_is_fixed (to_par)) { |
| to_par_n = gst_value_get_fraction_numerator (to_par); |
| to_par_d = gst_value_get_fraction_denominator (to_par); |
| |
| GST_DEBUG_OBJECT (trans, "PAR is fixed %d/%d", to_par_n, to_par_d); |
| |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d, |
| to_par_n, &num, &den)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| goto done; |
| } |
| |
| w = (guint) gst_util_uint64_scale_int (h, num, den); |
| gst_structure_fixate_field_nearest_int (outs, "width", w); |
| |
| goto done; |
| } |
| |
| /* The PAR is not fixed and it's quite likely that we can set |
| * an arbitrary PAR. */ |
| |
| /* Check if we can keep the input width */ |
| tmp = gst_structure_copy (outs); |
| gst_structure_fixate_field_nearest_int (tmp, "width", from_w); |
| gst_structure_get_int (tmp, "width", &set_w); |
| |
| /* Might have failed but try to keep the DAR nonetheless by |
| * adjusting the PAR */ |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, h, set_w, |
| &to_par_n, &to_par_d)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| gst_structure_free (tmp); |
| goto done; |
| } |
| |
| if (!gst_structure_has_field (tmp, "pixel-aspect-ratio")) |
| gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par); |
| gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio", |
| to_par_n, to_par_d); |
| gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n, |
| &set_par_d); |
| gst_structure_free (tmp); |
| |
| /* Check if the adjusted PAR is accepted */ |
| if (set_par_n == to_par_n && set_par_d == to_par_d) { |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio") || |
| set_par_n != set_par_d) |
| gst_structure_set (outs, "width", G_TYPE_INT, set_w, |
| "pixel-aspect-ratio", GST_TYPE_FRACTION, set_par_n, set_par_d, |
| NULL); |
| goto done; |
| } |
| |
| /* Otherwise scale the width to the new PAR and check if the |
| * adjusted with is accepted. If all that fails we can't keep |
| * the DAR */ |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d, |
| set_par_n, &num, &den)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| goto done; |
| } |
| |
| w = (guint) gst_util_uint64_scale_int (h, num, den); |
| gst_structure_fixate_field_nearest_int (outs, "width", w); |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio") || |
| set_par_n != set_par_d) |
| gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, |
| set_par_n, set_par_d, NULL); |
| |
| goto done; |
| } else if (w) { |
| GstStructure *tmp; |
| gint set_h, set_par_n, set_par_d; |
| |
| GST_DEBUG_OBJECT (trans, "width is fixed (%d)", w); |
| |
| /* If the PAR is fixed too, there's not much to do |
| * except choosing the height that is nearest to the |
| * height with the same DAR */ |
| if (gst_value_is_fixed (to_par)) { |
| to_par_n = gst_value_get_fraction_numerator (to_par); |
| to_par_d = gst_value_get_fraction_denominator (to_par); |
| |
| GST_DEBUG_OBJECT (trans, "PAR is fixed %d/%d", to_par_n, to_par_d); |
| |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d, |
| to_par_n, &num, &den)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| goto done; |
| } |
| |
| h = (guint) gst_util_uint64_scale_int (w, den, num); |
| gst_structure_fixate_field_nearest_int (outs, "height", h); |
| |
| goto done; |
| } |
| |
| /* The PAR is not fixed and it's quite likely that we can set |
| * an arbitrary PAR. */ |
| |
| /* Check if we can keep the input height */ |
| tmp = gst_structure_copy (outs); |
| gst_structure_fixate_field_nearest_int (tmp, "height", from_h); |
| gst_structure_get_int (tmp, "height", &set_h); |
| |
| /* Might have failed but try to keep the DAR nonetheless by |
| * adjusting the PAR */ |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_h, w, |
| &to_par_n, &to_par_d)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| gst_structure_free (tmp); |
| goto done; |
| } |
| if (!gst_structure_has_field (tmp, "pixel-aspect-ratio")) |
| gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par); |
| gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio", |
| to_par_n, to_par_d); |
| gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n, |
| &set_par_d); |
| gst_structure_free (tmp); |
| |
| /* Check if the adjusted PAR is accepted */ |
| if (set_par_n == to_par_n && set_par_d == to_par_d) { |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio") || |
| set_par_n != set_par_d) |
| gst_structure_set (outs, "height", G_TYPE_INT, set_h, |
| "pixel-aspect-ratio", GST_TYPE_FRACTION, set_par_n, set_par_d, |
| NULL); |
| goto done; |
| } |
| |
| /* Otherwise scale the height to the new PAR and check if the |
| * adjusted with is accepted. If all that fails we can't keep |
| * the DAR */ |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d, |
| set_par_n, &num, &den)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| goto done; |
| } |
| |
| h = (guint) gst_util_uint64_scale_int (w, den, num); |
| gst_structure_fixate_field_nearest_int (outs, "height", h); |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio") || |
| set_par_n != set_par_d) |
| gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, |
| set_par_n, set_par_d, NULL); |
| |
| goto done; |
| } else if (gst_value_is_fixed (to_par)) { |
| GstStructure *tmp; |
| gint set_h, set_w, f_h, f_w; |
| |
| to_par_n = gst_value_get_fraction_numerator (to_par); |
| to_par_d = gst_value_get_fraction_denominator (to_par); |
| |
| GST_DEBUG_OBJECT (trans, "PAR is fixed %d/%d", to_par_n, to_par_d); |
| |
| /* Calculate scale factor for the PAR change */ |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d, |
| to_par_n, &num, &den)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| goto done; |
| } |
| |
| /* Try to keep the input height (because of interlacing) */ |
| tmp = gst_structure_copy (outs); |
| gst_structure_fixate_field_nearest_int (tmp, "height", from_h); |
| gst_structure_get_int (tmp, "height", &set_h); |
| |
| /* This might have failed but try to scale the width |
| * to keep the DAR nonetheless */ |
| w = (guint) gst_util_uint64_scale_int (set_h, num, den); |
| gst_structure_fixate_field_nearest_int (tmp, "width", w); |
| gst_structure_get_int (tmp, "width", &set_w); |
| gst_structure_free (tmp); |
| |
| /* We kept the DAR and the height is nearest to the original height */ |
| if (set_w == w) { |
| gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height", |
| G_TYPE_INT, set_h, NULL); |
| goto done; |
| } |
| |
| f_h = set_h; |
| f_w = set_w; |
| |
| /* If the former failed, try to keep the input width at least */ |
| tmp = gst_structure_copy (outs); |
| gst_structure_fixate_field_nearest_int (tmp, "width", from_w); |
| gst_structure_get_int (tmp, "width", &set_w); |
| |
| /* This might have failed but try to scale the width |
| * to keep the DAR nonetheless */ |
| h = (guint) gst_util_uint64_scale_int (set_w, den, num); |
| gst_structure_fixate_field_nearest_int (tmp, "height", h); |
| gst_structure_get_int (tmp, "height", &set_h); |
| gst_structure_free (tmp); |
| |
| /* We kept the DAR and the width is nearest to the original width */ |
| if (set_h == h) { |
| gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height", |
| G_TYPE_INT, set_h, NULL); |
| goto done; |
| } |
| |
| /* If all this failed, keep the height that was nearest to the orignal |
| * height and the nearest possible width. This changes the DAR but |
| * there's not much else to do here. |
| */ |
| gst_structure_set (outs, "width", G_TYPE_INT, f_w, "height", G_TYPE_INT, |
| f_h, NULL); |
| goto done; |
| } else { |
| GstStructure *tmp; |
| gint set_h, set_w, set_par_n, set_par_d, tmp2; |
| |
| /* width, height and PAR are not fixed but passthrough is not possible */ |
| |
| /* First try to keep the height and width as good as possible |
| * and scale PAR */ |
| tmp = gst_structure_copy (outs); |
| gst_structure_fixate_field_nearest_int (tmp, "height", from_h); |
| gst_structure_get_int (tmp, "height", &set_h); |
| gst_structure_fixate_field_nearest_int (tmp, "width", from_w); |
| gst_structure_get_int (tmp, "width", &set_w); |
| |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_h, set_w, |
| &to_par_n, &to_par_d)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| gst_structure_free (tmp); |
| goto done; |
| } |
| |
| if (!gst_structure_has_field (tmp, "pixel-aspect-ratio")) |
| gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par); |
| gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio", |
| to_par_n, to_par_d); |
| gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n, |
| &set_par_d); |
| gst_structure_free (tmp); |
| |
| if (set_par_n == to_par_n && set_par_d == to_par_d) { |
| gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height", |
| G_TYPE_INT, set_h, NULL); |
| |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio") || |
| set_par_n != set_par_d) |
| gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, |
| set_par_n, set_par_d, NULL); |
| goto done; |
| } |
| |
| /* Otherwise try to scale width to keep the DAR with the set |
| * PAR and height */ |
| if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d, |
| set_par_n, &num, &den)) { |
| GST_ELEMENT_ERROR (trans, CORE, NEGOTIATION, (NULL), |
| ("Error calculating the output scaled size - integer overflow")); |
| goto done; |
| } |
| |
| w = (guint) gst_util_uint64_scale_int (set_h, num, den); |
| tmp = gst_structure_copy (outs); |
| gst_structure_fixate_field_nearest_int (tmp, "width", w); |
| gst_structure_get_int (tmp, "width", &tmp2); |
| gst_structure_free (tmp); |
| |
| if (tmp2 == w) { |
| gst_structure_set (outs, "width", G_TYPE_INT, tmp2, "height", |
| G_TYPE_INT, set_h, NULL); |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio") || |
| set_par_n != set_par_d) |
| gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, |
| set_par_n, set_par_d, NULL); |
| goto done; |
| } |
| |
| /* ... or try the same with the height */ |
| h = (guint) gst_util_uint64_scale_int (set_w, den, num); |
| tmp = gst_structure_copy (outs); |
| gst_structure_fixate_field_nearest_int (tmp, "height", h); |
| gst_structure_get_int (tmp, "height", &tmp2); |
| gst_structure_free (tmp); |
| |
| if (tmp2 == h) { |
| gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height", |
| G_TYPE_INT, tmp2, NULL); |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio") || |
| set_par_n != set_par_d) |
| gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, |
| set_par_n, set_par_d, NULL); |
| goto done; |
| } |
| |
| /* If all fails we can't keep the DAR and take the nearest values |
| * for everything from the first try */ |
| gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height", |
| G_TYPE_INT, set_h, NULL); |
| if (gst_structure_has_field (outs, "pixel-aspect-ratio") || |
| set_par_n != set_par_d) |
| gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, |
| set_par_n, set_par_d, NULL); |
| } |
| } |
| |
| done: |
| GST_DEBUG_OBJECT (trans, "fixated othercaps to %" GST_PTR_FORMAT, othercaps); |
| |
| if (from_par == &fpar) |
| g_value_unset (&fpar); |
| if (to_par == &tpar) |
| g_value_unset (&tpar); |
| |
| /* fixate remaining fields */ |
| othercaps = gst_caps_fixate (othercaps); |
| |
| if (direction == GST_PAD_SINK) { |
| if (gst_caps_is_subset (caps, othercaps)) { |
| gst_caps_replace (&othercaps, caps); |
| } |
| } |
| |
| return othercaps; |
| } |
| |
| static GstFlowReturn |
| gst_v4l2_transform_prepare_output_buffer (GstBaseTransform * trans, |
| GstBuffer * inbuf, GstBuffer ** outbuf) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans); |
| GstBufferPool *pool = GST_BUFFER_POOL (self->v4l2output->pool); |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_CLASS (parent_class); |
| |
| if (gst_base_transform_is_passthrough (trans)) { |
| GST_DEBUG_OBJECT (self, "Passthrough, no need to do anything"); |
| *outbuf = inbuf; |
| goto beach; |
| } |
| |
| /* Ensure input internal pool is active */ |
| if (!gst_buffer_pool_is_active (pool)) { |
| GstStructure *config = gst_buffer_pool_get_config (pool); |
| gint min = self->v4l2output->min_buffers == 0 ? GST_V4L2_MIN_BUFFERS : |
| self->v4l2output->min_buffers; |
| gst_buffer_pool_config_set_params (config, self->incaps, |
| self->v4l2output->info.size, min, min); |
| |
| /* There is no reason to refuse this config */ |
| if (!gst_buffer_pool_set_config (pool, config)) |
| goto activate_failed; |
| |
| if (!gst_buffer_pool_set_active (pool, TRUE)) |
| goto activate_failed; |
| } |
| |
| GST_DEBUG_OBJECT (self, "Queue input buffer"); |
| ret = gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL (pool), &inbuf); |
| if (G_UNLIKELY (ret != GST_FLOW_OK)) |
| goto beach; |
| |
| do { |
| pool = gst_base_transform_get_buffer_pool (trans); |
| |
| if (!gst_buffer_pool_set_active (pool, TRUE)) |
| goto activate_failed; |
| |
| GST_DEBUG_OBJECT (self, "Dequeue output buffer"); |
| ret = gst_buffer_pool_acquire_buffer (pool, outbuf, NULL); |
| g_object_unref (pool); |
| |
| if (ret != GST_FLOW_OK) |
| goto alloc_failed; |
| |
| pool = self->v4l2capture->pool; |
| ret = gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL (pool), outbuf); |
| |
| } while (ret == GST_V4L2_FLOW_CORRUPTED_BUFFER); |
| |
| if (ret != GST_FLOW_OK) { |
| gst_buffer_unref (*outbuf); |
| *outbuf = NULL; |
| } |
| |
| if (bclass->copy_metadata) |
| if (!bclass->copy_metadata (trans, inbuf, *outbuf)) { |
| /* something failed, post a warning */ |
| GST_ELEMENT_WARNING (self, STREAM, NOT_IMPLEMENTED, |
| ("could not copy metadata"), (NULL)); |
| } |
| |
| beach: |
| return ret; |
| |
| activate_failed: |
| GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, |
| ("failed to activate bufferpool"), ("failed to activate bufferpool")); |
| g_object_unref (pool); |
| return GST_FLOW_ERROR; |
| |
| alloc_failed: |
| GST_DEBUG_OBJECT (self, "could not allocate buffer from pool"); |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_v4l2_transform_transform (GstBaseTransform * trans, GstBuffer * inbuf, |
| GstBuffer * outbuf) |
| { |
| /* Nothing to do */ |
| return GST_FLOW_OK; |
| } |
| |
| static gboolean |
| gst_v4l2_transform_sink_event (GstBaseTransform * trans, GstEvent * event) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (trans); |
| gboolean ret; |
| |
| /* Nothing to flush in passthrough */ |
| if (gst_base_transform_is_passthrough (trans)) |
| return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_START: |
| GST_DEBUG_OBJECT (self, "flush start"); |
| gst_v4l2_object_unlock (self->v4l2output); |
| gst_v4l2_object_unlock (self->v4l2capture); |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_STOP: |
| /* Buffer should be back now */ |
| GST_DEBUG_OBJECT (self, "flush stop"); |
| gst_v4l2_object_unlock_stop (self->v4l2capture); |
| gst_v4l2_object_unlock_stop (self->v4l2output); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static GstStateChangeReturn |
| gst_v4l2_transform_change_state (GstElement * element, |
| GstStateChange transition) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (element); |
| GstStateChangeReturn ret; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| if (!gst_v4l2_transform_open (self)) |
| return GST_STATE_CHANGE_FAILURE; |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| gst_v4l2_object_unlock (self->v4l2output); |
| gst_v4l2_object_unlock (self->v4l2capture); |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| gst_v4l2_transform_close (self); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void |
| gst_v4l2_transform_dispose (GObject * object) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (object); |
| |
| gst_caps_replace (&self->probed_sinkcaps, NULL); |
| gst_caps_replace (&self->probed_srccaps, NULL); |
| |
| G_OBJECT_CLASS (parent_class)->dispose (object); |
| } |
| |
| static void |
| gst_v4l2_transform_finalize (GObject * object) |
| { |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (object); |
| |
| gst_v4l2_object_destroy (self->v4l2capture); |
| gst_v4l2_object_destroy (self->v4l2output); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_v4l2_transform_init (GstV4l2Transform * self) |
| { |
| /* V4L2 object are created in subinstance_init */ |
| /* enable QoS */ |
| gst_base_transform_set_qos_enabled (GST_BASE_TRANSFORM (self), TRUE); |
| } |
| |
| static void |
| gst_v4l2_transform_subinstance_init (GTypeInstance * instance, gpointer g_class) |
| { |
| GstV4l2TransformClass *klass = GST_V4L2_TRANSFORM_CLASS (g_class); |
| GstV4l2Transform *self = GST_V4L2_TRANSFORM (instance); |
| |
| self->v4l2output = gst_v4l2_object_new (GST_ELEMENT (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), |
| 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_v4l2_transform_class_init (GstV4l2TransformClass * klass) |
| { |
| GstElementClass *element_class; |
| GObjectClass *gobject_class; |
| GstBaseTransformClass *base_transform_class; |
| |
| element_class = (GstElementClass *) klass; |
| gobject_class = (GObjectClass *) klass; |
| base_transform_class = (GstBaseTransformClass *) klass; |
| |
| GST_DEBUG_CATEGORY_INIT (gst_v4l2_transform_debug, "v4l2transform", 0, |
| "V4L2 Converter"); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "V4L2 Video Converter", |
| "Filter/Converter/Video/Scaler", |
| "Transform streams via V4L2 API", |
| "Nicolas Dufresne <nicolas.dufresne@collabora.com>"); |
| |
| gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_v4l2_transform_dispose); |
| gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_v4l2_transform_finalize); |
| gobject_class->set_property = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_set_property); |
| gobject_class->get_property = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_get_property); |
| |
| base_transform_class->stop = GST_DEBUG_FUNCPTR (gst_v4l2_transform_stop); |
| base_transform_class->set_caps = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_set_caps); |
| base_transform_class->query = GST_DEBUG_FUNCPTR (gst_v4l2_transform_query); |
| base_transform_class->sink_event = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_sink_event); |
| base_transform_class->decide_allocation = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_decide_allocation); |
| base_transform_class->propose_allocation = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_propose_allocation); |
| base_transform_class->transform_caps = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_transform_caps); |
| base_transform_class->fixate_caps = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_fixate_caps); |
| base_transform_class->prepare_output_buffer = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_prepare_output_buffer); |
| base_transform_class->transform = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_transform); |
| |
| base_transform_class->passthrough_on_same_caps = TRUE; |
| |
| element_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_v4l2_transform_change_state); |
| |
| gst_v4l2_object_install_m2m_properties_helper (gobject_class); |
| } |
| |
| static void |
| gst_v4l2_transform_subclass_init (gpointer g_class, gpointer data) |
| { |
| GstV4l2TransformClass *klass = GST_V4L2_TRANSFORM_CLASS (g_class); |
| GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
| GstV4l2TransformCData *cdata = data; |
| |
| klass->default_device = cdata->device; |
| |
| /* Note: gst_pad_template_new() take the floating ref from the caps */ |
| gst_element_class_add_pad_template (element_class, |
| gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, |
| cdata->sink_caps)); |
| gst_element_class_add_pad_template (element_class, |
| gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, |
| cdata->src_caps)); |
| |
| g_free (cdata); |
| } |
| |
| /* Probing functions */ |
| gboolean |
| gst_v4l2_is_transform (GstCaps * sink_caps, GstCaps * src_caps) |
| { |
| gboolean ret = FALSE; |
| |
| if (gst_caps_is_subset (sink_caps, gst_v4l2_object_get_raw_caps ()) |
| && gst_caps_is_subset (src_caps, gst_v4l2_object_get_raw_caps ())) |
| ret = TRUE; |
| |
| return ret; |
| } |
| |
| gboolean |
| gst_v4l2_transform_register (GstPlugin * plugin, const gchar * basename, |
| const gchar * device_path, GstCaps * sink_caps, GstCaps * src_caps) |
| { |
| GTypeQuery type_query; |
| GTypeInfo type_info = { 0, }; |
| GType type, subtype; |
| gchar *type_name; |
| GstV4l2TransformCData *cdata; |
| |
| cdata = g_new0 (GstV4l2TransformCData, 1); |
| cdata->device = g_strdup (device_path); |
| cdata->sink_caps = gst_caps_ref (sink_caps); |
| cdata->src_caps = gst_caps_ref (src_caps); |
| |
| type = gst_v4l2_transform_get_type (); |
| g_type_query (type, &type_query); |
| memset (&type_info, 0, sizeof (type_info)); |
| type_info.class_size = type_query.class_size; |
| type_info.instance_size = type_query.instance_size; |
| type_info.class_init = gst_v4l2_transform_subclass_init; |
| type_info.class_data = cdata; |
| type_info.instance_init = gst_v4l2_transform_subinstance_init; |
| |
| type_name = g_strdup_printf ("v4l2%sconvert", basename); |
| subtype = g_type_register_static (type, type_name, &type_info, 0); |
| |
| gst_element_register (plugin, type_name, GST_RANK_NONE, subtype); |
| |
| g_free (type_name); |
| |
| return TRUE; |
| } |