/* GStreamer * Copyright (C) <2016> Carlos Rafael Giani * * 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. */ /** * SECTION:element-rawvideoparse * * This element parses incoming data as raw video frames and timestamps these. * It also handles seek queries in said raw video data, and ensures that output * buffers contain exactly one frame, even if the input buffers contain only * partial frames or multiple frames. In the former case, it will continue to * receive buffers until there is enough input data to output one frame. In the * latter case, it will extract the first frame in the buffer and output it, then * the second one etc. until the remaining unparsed bytes aren't enough to form * a complete frame, and it will then continue as described in the earlier case. * * The element implements the properties and sink caps configuration as specified * in the #GstRawBaseParse documentation. The properties configuration can be * modified by using the width, height, pixel-aspect-ratio, framerate, interlaced, * top-field-first, plane-strides, plane-offsets, and frame-stride properties. * * If the properties configuration is used, plane strides and offsets will be * computed by using gst_video_info_set_format(). This can be overridden by passing * GValueArrays to the plane-offsets and plane-strides properties. When this is * done, these custom offsets and strides are used later even if new width, * height, format etc. property values might be set. To switch back to computed * plane strides & offsets, pass NULL to one or both of the plane-offset and * plane-array properties. * * The frame stride property is useful in cases where there is extra data between * the frames (for example, trailing metadata, or headers). The parser calculates * the actual frame size out of the other properties and compares it with this * frame-stride value. If the frame stride is larger than the calculated size, * then the extra bytes after the end of the frame are skipped. For example, with * 8-bit grayscale frames and a frame size of 100x10 pixels and a frame stride of * 1500 bytes, there are 500 excess bytes at the end of the actual frame which * are then skipped. It is safe to set the frame stride to a value that is smaller * than the actual frame size (in fact, its default value is 0); if it is smaller, * then no trailing data will be skipped. * * If a framerate of 0 Hz is set (for example, 0/1), then output buffers will have * no duration set. The first output buffer will have a PTS 0, all subsequent ones * an unset PTS. * * * Example pipelines * |[ * gst-launch-1.0 filesrc location=video.raw ! rawvideoparse use-sink-caps=false \ * width=500 height=400 format=y444 ! autovideosink * ]| Read raw data from a local file and parse it as video data with 500x400 pixels * and Y444 video format. * |[ * gst-launch-1.0 filesrc location=video.raw ! queue ! "video/x-raw, width=320, \ * height=240, format=I420, framerate=1/1" ! rawvideoparse \ * use-sink-caps=true ! autovideosink * ]| Read raw data from a local file and parse it as video data with 320x240 pixels * and I420 video format. The queue element here is to force push based scheduling. * See the documentation in #GstRawBaseParse for the reason why. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* FIXME: GValueArray is deprecated, but there is currently no viabla alternative * See https://bugzilla.gnome.org/show_bug.cgi?id=667228 */ #define GLIB_DISABLE_DEPRECATION_WARNINGS #include #include "gstrawvideoparse.h" #include "unalignedvideo.h" GST_DEBUG_CATEGORY_STATIC (raw_video_parse_debug); #define GST_CAT_DEFAULT raw_video_parse_debug enum { PROP_0, PROP_WIDTH, PROP_HEIGHT, PROP_FORMAT, PROP_PIXEL_ASPECT_RATIO, PROP_FRAMERATE, PROP_INTERLACED, PROP_TOP_FIELD_FIRST, PROP_PLANE_STRIDES, PROP_PLANE_OFFSETS, PROP_FRAME_STRIDE }; #define DEFAULT_WIDTH 320 #define DEFAULT_HEIGHT 240 #define DEFAULT_FORMAT GST_VIDEO_FORMAT_I420 #define DEFAULT_PIXEL_ASPECT_RATIO_N 1 #define DEFAULT_PIXEL_ASPECT_RATIO_D 1 #define DEFAULT_FRAMERATE_N 25 #define DEFAULT_FRAMERATE_D 1 #define DEFAULT_INTERLACED FALSE #define DEFAULT_TOP_FIELD_FIRST FALSE #define DEFAULT_FRAME_STRIDE 0 #define GST_RAW_VIDEO_PARSE_CAPS \ GST_VIDEO_CAPS_MAKE(GST_VIDEO_FORMATS_ALL) "; " static GstStaticPadTemplate static_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_UNALIGNED_RAW_VIDEO_CAPS "; " GST_RAW_VIDEO_PARSE_CAPS) ); static GstStaticPadTemplate static_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_RAW_VIDEO_PARSE_CAPS) ); #define gst_raw_video_parse_parent_class parent_class G_DEFINE_TYPE (GstRawVideoParse, gst_raw_video_parse, GST_TYPE_RAW_BASE_PARSE); static void gst_raw_video_parse_set_property (GObject * object, guint prop_id, GValue const *value, GParamSpec * pspec); static void gst_raw_video_parse_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static gboolean gst_raw_video_parse_stop (GstBaseParse * parse); static gboolean gst_raw_video_parse_set_current_config (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config); static GstRawBaseParseConfig gst_raw_video_parse_get_current_config (GstRawBaseParse * raw_base_parse); static gboolean gst_raw_video_parse_set_config_from_caps (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config, GstCaps * caps); static gboolean gst_raw_video_parse_get_caps_from_config (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config, GstCaps ** caps); static gsize gst_raw_video_parse_get_config_frame_size (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config); static guint gst_raw_video_parse_get_max_frames_per_buffer (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config); static gboolean gst_raw_video_parse_is_config_ready (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config); static gboolean gst_raw_video_parse_process (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config, GstBuffer * in_data, gsize total_num_in_bytes, gsize num_valid_in_bytes, GstBuffer ** processed_data); static gboolean gst_raw_video_parse_is_unit_format_supported (GstRawBaseParse * raw_base_parse, GstFormat format); static void gst_raw_video_parse_get_units_per_second (GstRawBaseParse * raw_base_parse, GstFormat format, GstRawBaseParseConfig config, gsize * units_per_sec_n, gsize * units_per_sec_d); static gint gst_raw_video_parse_get_overhead_size (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config); static gboolean gst_raw_video_parse_is_using_sink_caps (GstRawVideoParse * raw_video_parse); static GstRawVideoParseConfig * gst_raw_video_parse_get_config_ptr (GstRawVideoParse * raw_video_parse, GstRawBaseParseConfig config); static void gst_raw_video_parse_init_config (GstRawVideoParseConfig * config); static void gst_raw_video_parse_update_info (GstRawVideoParseConfig * config); static void gst_raw_video_parse_class_init (GstRawVideoParseClass * klass) { GObjectClass *object_class; GstElementClass *element_class; GstBaseParseClass *baseparse_class; GstRawBaseParseClass *rawbaseparse_class; GST_DEBUG_CATEGORY_INIT (raw_video_parse_debug, "rawvideoparse", 0, "rawvideoparse element"); object_class = G_OBJECT_CLASS (klass); element_class = GST_ELEMENT_CLASS (klass); baseparse_class = GST_BASE_PARSE_CLASS (klass); rawbaseparse_class = GST_RAW_BASE_PARSE_CLASS (klass); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&static_sink_template)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&static_src_template)); object_class->set_property = GST_DEBUG_FUNCPTR (gst_raw_video_parse_set_property); object_class->get_property = GST_DEBUG_FUNCPTR (gst_raw_video_parse_get_property); baseparse_class->stop = GST_DEBUG_FUNCPTR (gst_raw_video_parse_stop); rawbaseparse_class->set_current_config = GST_DEBUG_FUNCPTR (gst_raw_video_parse_set_current_config); rawbaseparse_class->get_current_config = GST_DEBUG_FUNCPTR (gst_raw_video_parse_get_current_config); rawbaseparse_class->set_config_from_caps = GST_DEBUG_FUNCPTR (gst_raw_video_parse_set_config_from_caps); rawbaseparse_class->get_caps_from_config = GST_DEBUG_FUNCPTR (gst_raw_video_parse_get_caps_from_config); rawbaseparse_class->get_config_frame_size = GST_DEBUG_FUNCPTR (gst_raw_video_parse_get_config_frame_size); rawbaseparse_class->get_max_frames_per_buffer = GST_DEBUG_FUNCPTR (gst_raw_video_parse_get_max_frames_per_buffer); rawbaseparse_class->is_config_ready = GST_DEBUG_FUNCPTR (gst_raw_video_parse_is_config_ready); rawbaseparse_class->process = GST_DEBUG_FUNCPTR (gst_raw_video_parse_process); rawbaseparse_class->is_unit_format_supported = GST_DEBUG_FUNCPTR (gst_raw_video_parse_is_unit_format_supported); rawbaseparse_class->get_units_per_second = GST_DEBUG_FUNCPTR (gst_raw_video_parse_get_units_per_second); rawbaseparse_class->get_overhead_size = GST_DEBUG_FUNCPTR (gst_raw_video_parse_get_overhead_size); g_object_class_install_property (object_class, PROP_WIDTH, g_param_spec_int ("width", "Width", "Width of frames in raw stream", 0, G_MAXINT, DEFAULT_WIDTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_HEIGHT, g_param_spec_int ("height", "Height", "Height of frames in raw stream", 0, G_MAXINT, DEFAULT_HEIGHT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_FORMAT, g_param_spec_enum ("format", "Format", "Format of frames in raw stream", GST_TYPE_VIDEO_FORMAT, DEFAULT_FORMAT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_FRAMERATE, gst_param_spec_fraction ("framerate", "Frame rate", "Rate of frames in raw stream", 0, 1, G_MAXINT, 1, DEFAULT_FRAMERATE_N, DEFAULT_FRAMERATE_D, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_PIXEL_ASPECT_RATIO, gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel aspect ratio", "Pixel aspect ratio of frames in raw stream", 1, 100, 100, 1, DEFAULT_PIXEL_ASPECT_RATIO_N, DEFAULT_PIXEL_ASPECT_RATIO_D, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_INTERLACED, g_param_spec_boolean ("interlaced", "Interlaced flag", "True if frames in raw stream are interlaced", DEFAULT_INTERLACED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_TOP_FIELD_FIRST, g_param_spec_boolean ("top-field-first", "Top field first", "True if top field in frames in raw stream come first (not used if frames aren't interlaced)", DEFAULT_INTERLACED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_PLANE_STRIDES, g_param_spec_value_array ("plane-strides", "Plane strides", "Strides of the planets in bytes", g_param_spec_uint ("plane-stride", "Plane stride", "Stride of the n-th plane in bytes (0 = stride equals width*bytes-per-pixel)", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_PLANE_OFFSETS, g_param_spec_value_array ("plane-offsets", "Plane offsets", "Offsets of the planets in bytes", g_param_spec_uint ("plane-offset", "Plane offset", "Offset of the n-th plane in bytes", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); g_object_class_install_property (object_class, PROP_FRAME_STRIDE, g_param_spec_uint ("frame-stride", "Frame stride", "Stride between whole frames (0 = frames are tightly packed together)", 0, G_MAXUINT, DEFAULT_FRAME_STRIDE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); gst_element_class_set_static_metadata (element_class, "rawvideoparse", "Codec/Parser/Video", "Converts unformatted data streams into timestamped raw video frames", "Carlos Rafael Giani "); } static void gst_raw_video_parse_init (GstRawVideoParse * raw_video_parse) { gst_raw_video_parse_init_config (&(raw_video_parse->properties_config)); gst_raw_video_parse_init_config (&(raw_video_parse->sink_caps_config)); /* As required by GstRawBaseParse, ensure that the current configuration * is initially set to be the properties config */ raw_video_parse->current_config = &(raw_video_parse->properties_config); /* Properties config must be valid from the start, so set its ready value * to TRUE, and make sure its bpf value is valid. */ raw_video_parse->properties_config.ready = TRUE; raw_video_parse->properties_config.top_field_first = DEFAULT_TOP_FIELD_FIRST; raw_video_parse->properties_config.frame_stride = DEFAULT_FRAME_STRIDE; } static void gst_raw_video_parse_set_property (GObject * object, guint prop_id, GValue const *value, GParamSpec * pspec) { GstBaseParse *base_parse = GST_BASE_PARSE (object); GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (object); GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (object); GstRawVideoParseConfig *props_cfg = &(raw_video_parse->properties_config); /* All properties are handled similarly: * - if the new value is the same as the current value, nothing is done * - the parser lock is held while the new value is set * - if the properties config is the current config, the source caps are * invalidated to ensure that the code in handle_frame pushes a new CAPS * event out * - properties that affect the video frame size call the function to update * the info and also call gst_base_parse_set_min_frame_size() to ensure * that the minimum frame size can hold 1 frame (= one sample for each * channel); to ensure that the min frame size includes any extra padding, * it is set to the result of gst_raw_video_parse_get_config_frame_size() * - property configuration values that require video info updates aren't * written directory into the video info structure, but in the extra * fields instead (gst_raw_video_parse_update_info() then copies the values * from these fields into the video info); see the documentation inside * gst_raw_video_parse_update_info() for the reason why */ switch (prop_id) { case PROP_WIDTH: { gint new_width = g_value_get_int (value); GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); if (new_width != props_cfg->width) { props_cfg->width = new_width; gst_raw_video_parse_update_info (props_cfg); if (!gst_raw_video_parse_is_using_sink_caps (raw_video_parse)) { gst_raw_base_parse_invalidate_src_caps (raw_base_parse); gst_base_parse_set_min_frame_size (base_parse, gst_raw_video_parse_get_config_frame_size (raw_base_parse, GST_RAW_BASE_PARSE_CONFIG_PROPERTIES)); } } GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_HEIGHT: { gint new_height = g_value_get_int (value); GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); if (new_height != props_cfg->height) { props_cfg->height = new_height; gst_raw_video_parse_update_info (props_cfg); if (!gst_raw_video_parse_is_using_sink_caps (raw_video_parse)) { gst_raw_base_parse_invalidate_src_caps (raw_base_parse); gst_base_parse_set_min_frame_size (base_parse, gst_raw_video_parse_get_config_frame_size (raw_base_parse, GST_RAW_BASE_PARSE_CONFIG_PROPERTIES)); } } GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_FORMAT: { GstVideoFormat new_format = g_value_get_enum (value); GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); if (new_format != props_cfg->format) { props_cfg->format = new_format; gst_raw_video_parse_update_info (props_cfg); if (!gst_raw_video_parse_is_using_sink_caps (raw_video_parse)) { gst_raw_base_parse_invalidate_src_caps (raw_base_parse); gst_base_parse_set_min_frame_size (base_parse, gst_raw_video_parse_get_config_frame_size (raw_base_parse, GST_RAW_BASE_PARSE_CONFIG_PROPERTIES)); } } GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_PIXEL_ASPECT_RATIO: { GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); /* The pixel aspect ratio does not affect the video frame size, * so it is just set directly without any updates */ props_cfg->pixel_aspect_ratio_n = GST_VIDEO_INFO_PAR_N (&(props_cfg->info)) = gst_value_get_fraction_numerator (value); props_cfg->pixel_aspect_ratio_d = GST_VIDEO_INFO_PAR_D (&(props_cfg->info)) = gst_value_get_fraction_denominator (value); GST_DEBUG_OBJECT (raw_video_parse, "setting pixel aspect ratio to %u/%u", props_cfg->pixel_aspect_ratio_n, props_cfg->pixel_aspect_ratio_d); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_FRAMERATE: { GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); /* The framerate does not affect the video frame size, * so it is just set directly without any updates */ props_cfg->framerate_n = GST_VIDEO_INFO_FPS_N (&(props_cfg->info)) = gst_value_get_fraction_numerator (value); props_cfg->framerate_d = GST_VIDEO_INFO_FPS_D (&(props_cfg->info)) = gst_value_get_fraction_denominator (value); GST_DEBUG_OBJECT (raw_video_parse, "setting framerate to %u/%u", props_cfg->framerate_n, props_cfg->framerate_d); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_INTERLACED: { GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); /* Interlacing does not affect the video frame size, * so it is just set directly without any updates */ props_cfg->interlaced = g_value_get_boolean (value); GST_VIDEO_INFO_INTERLACE_MODE (&(props_cfg->info)) = props_cfg->interlaced ? GST_VIDEO_INTERLACE_MODE_INTERLEAVED : GST_VIDEO_INTERLACE_MODE_PROGRESSIVE; GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_TOP_FIELD_FIRST: { /* The top-field-first flag is a detail related to * interlacing, so no video info update is needed */ GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); props_cfg->top_field_first = g_value_get_boolean (value); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_PLANE_STRIDES: { GValueArray *valarray = g_value_get_boxed (value); guint n_planes; guint i; /* If no valarray is given, then disable custom * plane strides & offsets and stick to the * standard computed ones */ if (valarray == NULL) { GST_DEBUG_OBJECT (raw_video_parse, "custom plane strides & offsets disabled"); props_cfg->custom_plane_strides = FALSE; gst_raw_video_parse_update_info (props_cfg); break; } /* Sanity check - reject empty arrays */ if ((valarray != NULL) && (valarray->n_values == 0)) { GST_ELEMENT_ERROR (raw_video_parse, LIBRARY, SETTINGS, ("plane strides property holds an empty array"), (NULL)); break; } GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); n_planes = GST_VIDEO_INFO_N_PLANES (&(props_cfg->info)); /* Check that the valarray holds the right number of values */ if (valarray->n_values != n_planes) { GST_ELEMENT_ERROR (raw_video_parse, LIBRARY, SETTINGS, ("incorrect number of elements in plane strides property"), ("expected: %u, got: %u", n_planes, valarray->n_values)); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } /* Copy the values to the stride array */ for (i = 0; i < n_planes; ++i) { GValue *val = g_value_array_get_nth (valarray, i); props_cfg->plane_strides[i] = g_value_get_uint (val); GST_DEBUG_OBJECT (raw_video_parse, "plane #%u stride: %d", i, props_cfg->plane_strides[i]); } props_cfg->custom_plane_strides = TRUE; gst_raw_video_parse_update_info (props_cfg); if (!gst_raw_video_parse_is_using_sink_caps (raw_video_parse)) gst_base_parse_set_min_frame_size (base_parse, gst_raw_video_parse_get_config_frame_size (raw_base_parse, GST_RAW_BASE_PARSE_CONFIG_PROPERTIES)); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_PLANE_OFFSETS: { GValueArray *valarray = g_value_get_boxed (value); guint n_planes; guint i; /* If no valarray is given, then disable custom * plane strides & offsets and stick to the * standard computed ones */ if (valarray == NULL) { GST_DEBUG_OBJECT (raw_video_parse, "custom plane strides & offsets disabled"); props_cfg->custom_plane_strides = FALSE; gst_raw_video_parse_update_info (props_cfg); break; } /* Sanity check - reject empty arrays */ if ((valarray != NULL) && (valarray->n_values == 0)) { GST_ELEMENT_ERROR (raw_video_parse, LIBRARY, SETTINGS, ("plane offsets property holds an empty array"), (NULL)); break; } GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); n_planes = GST_VIDEO_INFO_N_PLANES (&(props_cfg->info)); /* Check that the valarray holds the right number of values */ if (valarray->n_values != n_planes) { GST_ELEMENT_ERROR (raw_video_parse, LIBRARY, SETTINGS, ("incorrect number of elements in plane offsets property"), ("expected: %u, got: %u", n_planes, valarray->n_values)); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } /* Copy the values to the offset array */ for (i = 0; i < n_planes; ++i) { GValue *val = g_value_array_get_nth (valarray, i); props_cfg->plane_offsets[i] = g_value_get_uint (val); GST_DEBUG_OBJECT (raw_video_parse, "plane #%u offset: %" G_GSIZE_FORMAT, i, props_cfg->plane_offsets[i]); } props_cfg->custom_plane_strides = TRUE; gst_raw_video_parse_update_info (props_cfg); if (!gst_raw_video_parse_is_using_sink_caps (raw_video_parse)) gst_base_parse_set_min_frame_size (base_parse, gst_raw_video_parse_get_config_frame_size (raw_base_parse, GST_RAW_BASE_PARSE_CONFIG_PROPERTIES)); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } case PROP_FRAME_STRIDE: { /* The frame stride does not affect the video frame size, * so it is just set directly without any updates */ GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); props_cfg->frame_stride = g_value_get_uint (value); gst_raw_video_parse_update_info (props_cfg); if (!gst_raw_video_parse_is_using_sink_caps (raw_video_parse)) gst_base_parse_set_min_frame_size (base_parse, gst_raw_video_parse_get_config_frame_size (raw_base_parse, GST_RAW_BASE_PARSE_CONFIG_PROPERTIES)); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_raw_video_parse_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (object); GstRawVideoParseConfig *props_cfg = &(raw_video_parse->properties_config); switch (prop_id) { case PROP_WIDTH: GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); g_value_set_int (value, props_cfg->width); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; case PROP_HEIGHT: GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); g_value_set_int (value, props_cfg->height); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; case PROP_FORMAT: GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); g_value_set_enum (value, props_cfg->format); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; case PROP_PIXEL_ASPECT_RATIO: GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); gst_value_set_fraction (value, props_cfg->pixel_aspect_ratio_n, props_cfg->pixel_aspect_ratio_d); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; case PROP_FRAMERATE: GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); gst_value_set_fraction (value, props_cfg->framerate_n, props_cfg->framerate_d); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; case PROP_INTERLACED: GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); g_value_set_boolean (value, props_cfg->interlaced); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; case PROP_TOP_FIELD_FIRST: GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); g_value_set_boolean (value, props_cfg->top_field_first); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; case PROP_PLANE_STRIDES: { guint i, n_planes; GValue val = G_VALUE_INIT; GValueArray *valarray; GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); n_planes = GST_VIDEO_INFO_N_PLANES (&(props_cfg->info)); valarray = g_value_array_new (n_planes); g_value_init (&val, G_TYPE_UINT); for (i = 0; i < n_planes; ++i) { g_value_set_uint (&val, props_cfg->plane_strides[i]); g_value_array_insert (valarray, i, &val); } g_value_unset (&val); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); /* Pass on ownership to the value array, * since we don't need it anymore */ g_value_take_boxed (value, valarray); break; } case PROP_PLANE_OFFSETS: { guint i, n_planes; GValue val = G_VALUE_INIT; GValueArray *valarray; GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); n_planes = GST_VIDEO_INFO_N_PLANES (&(props_cfg->info)); valarray = g_value_array_new (n_planes); g_value_init (&val, G_TYPE_UINT); for (i = 0; i < n_planes; ++i) { g_value_set_uint (&val, props_cfg->plane_offsets[i]); g_value_array_insert (valarray, i, &val); } g_value_unset (&val); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); /* Pass on ownership to the value array, * since we don't need it anymore */ g_value_take_boxed (value, valarray); break; } case PROP_FRAME_STRIDE: GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object); g_value_set_uint (value, raw_video_parse->properties_config.frame_stride); GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gst_raw_video_parse_stop (GstBaseParse * parse) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (parse); /* Sink caps config is not ready until caps come in. * We are stopping processing, the element is being reset, * so the config has to be un-readied. * (Since the properties config is not depending on caps, * its ready status is always TRUE.) */ raw_video_parse->sink_caps_config.ready = FALSE; return GST_BASE_PARSE_CLASS (parent_class)->stop (parse); } static gboolean gst_raw_video_parse_set_current_config (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); switch (config) { case GST_RAW_BASE_PARSE_CONFIG_PROPERTIES: raw_video_parse->current_config = &(raw_video_parse->properties_config); break; case GST_RAW_BASE_PARSE_CONFIG_SINKCAPS: raw_video_parse->current_config = &(raw_video_parse->sink_caps_config); break; default: g_assert_not_reached (); } return TRUE; } static GstRawBaseParseConfig gst_raw_video_parse_get_current_config (GstRawBaseParse * raw_base_parse) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); return gst_raw_video_parse_is_using_sink_caps (raw_video_parse) ? GST_RAW_BASE_PARSE_CONFIG_SINKCAPS : GST_RAW_BASE_PARSE_CONFIG_PROPERTIES; } static gboolean gst_raw_video_parse_set_config_from_caps (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config, GstCaps * caps) { int i; GstStructure *structure; GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); GstRawVideoParseConfig *config_ptr = gst_raw_video_parse_get_config_ptr (raw_video_parse, config); g_assert (caps != NULL); /* Caps might get copied, and the copy needs to be unref'd. * Also, the caller retains ownership over the original caps. * So, to make this mechanism also work with cases where the * caps are *not* copied, ref the original caps here first. */ gst_caps_ref (caps); structure = gst_caps_get_structure (caps, 0); /* For unaligned raw data, the output caps stay the same, * except that video/x-unaligned-raw becomes video/x-raw, * since the parser aligns the frame data */ if (gst_structure_has_name (structure, "video/x-unaligned-raw")) { /* Copy the caps to be able to modify them */ GstCaps *new_caps = gst_caps_copy (caps); gst_caps_unref (caps); caps = new_caps; /* Change the media type to video/x-raw , otherwise * gst_video_info_from_caps() won't work */ structure = gst_caps_get_structure (caps, 0); gst_structure_set_name (structure, "video/x-raw"); } config_ptr->ready = gst_video_info_from_caps (&(config_ptr->info), caps); if (config_ptr->ready) { config_ptr->width = GST_VIDEO_INFO_WIDTH (&(config_ptr->info)); config_ptr->height = GST_VIDEO_INFO_HEIGHT (&(config_ptr->info)); config_ptr->pixel_aspect_ratio_n = GST_VIDEO_INFO_PAR_N (&(config_ptr->info)); config_ptr->pixel_aspect_ratio_d = GST_VIDEO_INFO_PAR_D (&(config_ptr->info)); config_ptr->framerate_n = GST_VIDEO_INFO_FPS_N (&(config_ptr->info)); config_ptr->framerate_d = GST_VIDEO_INFO_FPS_D (&(config_ptr->info)); config_ptr->interlaced = GST_VIDEO_INFO_IS_INTERLACED (&(config_ptr->info)); config_ptr->height = GST_VIDEO_INFO_HEIGHT (&(config_ptr->info)); config_ptr->top_field_first = 0; config_ptr->frame_stride = 0; for (i = 0; i < GST_VIDEO_MAX_PLANES; ++i) { config_ptr->plane_offsets[i] = GST_VIDEO_INFO_PLANE_OFFSET (&(config_ptr->info), i); config_ptr->plane_strides[i] = GST_VIDEO_INFO_PLANE_STRIDE (&(config_ptr->info), i); } } gst_caps_unref (caps); return config_ptr->ready; } static gboolean gst_raw_video_parse_get_caps_from_config (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config, GstCaps ** caps) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); GstRawVideoParseConfig *config_ptr = gst_raw_video_parse_get_config_ptr (raw_video_parse, config); g_assert (caps != NULL); *caps = gst_video_info_to_caps (&(config_ptr->info)); return *caps != NULL; } static gsize gst_raw_video_parse_get_config_frame_size (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); GstRawVideoParseConfig *config_ptr = gst_raw_video_parse_get_config_ptr (raw_video_parse, config); return MAX (GST_VIDEO_INFO_SIZE (&(config_ptr->info)), (gsize) (config_ptr->frame_stride)); } static guint gst_raw_video_parse_get_max_frames_per_buffer (G_GNUC_UNUSED GstRawBaseParse * raw_base_parse, G_GNUC_UNUSED GstRawBaseParseConfig config) { /* We want exactly one frame per buffer */ return 1; } static gboolean gst_raw_video_parse_is_config_ready (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); return gst_raw_video_parse_get_config_ptr (raw_video_parse, config)->ready; } static gboolean gst_raw_video_parse_process (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config, GstBuffer * in_data, G_GNUC_UNUSED gsize total_num_in_bytes, G_GNUC_UNUSED gsize num_valid_in_bytes, GstBuffer ** processed_data) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); GstRawVideoParseConfig *config_ptr = gst_raw_video_parse_get_config_ptr (raw_video_parse, config); guint frame_flags = 0; GstVideoInfo *video_info = &(config_ptr->info); GstVideoMeta *videometa; GstBuffer *out_data; /* In case of extra padding bytes, get a subbuffer without the padding bytes. * Otherwise, just add the video meta. */ if (GST_VIDEO_INFO_SIZE (video_info) < config_ptr->frame_stride) { *processed_data = out_data = gst_buffer_copy_region (in_data, GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_MEMORY, 0, GST_VIDEO_INFO_SIZE (video_info)); } else { out_data = in_data; *processed_data = NULL; } if (config_ptr->interlaced) { GST_BUFFER_FLAG_SET (out_data, GST_VIDEO_BUFFER_FLAG_INTERLACED); frame_flags |= GST_VIDEO_FRAME_FLAG_INTERLACED; if (config_ptr->top_field_first) { GST_BUFFER_FLAG_SET (out_data, GST_VIDEO_BUFFER_FLAG_TFF); frame_flags |= GST_VIDEO_FRAME_FLAG_TFF; } else GST_BUFFER_FLAG_UNSET (out_data, GST_VIDEO_BUFFER_FLAG_TFF); } /* Remove any existing videometa - it will be replaced by the new videometa * from here */ while ((videometa = gst_buffer_get_video_meta (out_data))) { GST_LOG_OBJECT (raw_base_parse, "removing existing videometa from buffer"); gst_buffer_remove_meta (out_data, (GstMeta *) videometa); } gst_buffer_add_video_meta_full (out_data, frame_flags, config_ptr->format, config_ptr->width, config_ptr->height, GST_VIDEO_INFO_N_PLANES (video_info), config_ptr->plane_offsets, config_ptr->plane_strides); return TRUE; } static gboolean gst_raw_video_parse_is_unit_format_supported (G_GNUC_UNUSED GstRawBaseParse * raw_base_parse, GstFormat format) { switch (format) { case GST_FORMAT_BYTES: case GST_FORMAT_DEFAULT: return TRUE; default: return FALSE; } } static void gst_raw_video_parse_get_units_per_second (GstRawBaseParse * raw_base_parse, GstFormat format, GstRawBaseParseConfig config, gsize * units_per_sec_n, gsize * units_per_sec_d) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); GstRawVideoParseConfig *config_ptr = gst_raw_video_parse_get_config_ptr (raw_video_parse, config); switch (format) { case GST_FORMAT_BYTES: { gsize framesize = GST_VIDEO_INFO_SIZE (&(config_ptr->info)); gint64 n = framesize * config_ptr->framerate_n; gint64 d = config_ptr->framerate_d; gint64 common_div = gst_util_greatest_common_divisor_int64 (n, d); GST_DEBUG_OBJECT (raw_video_parse, "n: %" G_GINT64_FORMAT " d: %" G_GINT64_FORMAT " common divisor: %" G_GINT64_FORMAT, n, d, common_div); /* Divide numerator and denominator by greatest common divisor. * This minimizes the risk of integer overflows in the baseparse class. */ *units_per_sec_n = n / common_div; *units_per_sec_d = d / common_div; break; } case GST_FORMAT_DEFAULT: { *units_per_sec_n = config_ptr->framerate_n; *units_per_sec_d = config_ptr->framerate_d; break; } default: g_assert_not_reached (); } } static gint gst_raw_video_parse_get_overhead_size (GstRawBaseParse * raw_base_parse, GstRawBaseParseConfig config) { GstRawVideoParse *raw_video_parse = GST_RAW_VIDEO_PARSE (raw_base_parse); GstRawVideoParseConfig *config_ptr = gst_raw_video_parse_get_config_ptr (raw_video_parse, config); gint64 frame_size = GST_VIDEO_INFO_SIZE (&(config_ptr->info)); gint64 frame_stride = config_ptr->frame_stride; /* In the video parser, the overhead is defined by the difference between * the frame stride and the actual frame size. If the former is larger, * then the additional bytes are considered padding bytes and get ignored * by the base class. */ GST_LOG_OBJECT (raw_video_parse, "frame size: %" G_GINT64_FORMAT " frame stride: %" G_GINT64_FORMAT, frame_size, frame_stride); return (frame_size < frame_stride) ? (gint) (frame_stride - frame_size) : 0; } static gboolean gst_raw_video_parse_is_using_sink_caps (GstRawVideoParse * raw_video_parse) { return raw_video_parse->current_config == &(raw_video_parse->sink_caps_config); } static GstRawVideoParseConfig * gst_raw_video_parse_get_config_ptr (GstRawVideoParse * raw_video_parse, GstRawBaseParseConfig config) { g_assert (raw_video_parse->current_config != NULL); switch (config) { case GST_RAW_BASE_PARSE_CONFIG_PROPERTIES: return &(raw_video_parse->properties_config); case GST_RAW_BASE_PARSE_CONFIG_SINKCAPS: return &(raw_video_parse->sink_caps_config); default: g_assert (raw_video_parse->current_config != NULL); return raw_video_parse->current_config; } } static void gst_raw_video_parse_init_config (GstRawVideoParseConfig * config) { int i; config->ready = FALSE; config->width = DEFAULT_WIDTH; config->height = DEFAULT_HEIGHT; config->format = DEFAULT_FORMAT; config->pixel_aspect_ratio_n = DEFAULT_PIXEL_ASPECT_RATIO_N; config->pixel_aspect_ratio_d = DEFAULT_PIXEL_ASPECT_RATIO_D; config->framerate_n = DEFAULT_FRAMERATE_N; config->framerate_d = DEFAULT_FRAMERATE_D; config->interlaced = DEFAULT_INTERLACED; config->top_field_first = DEFAULT_TOP_FIELD_FIRST; config->frame_stride = DEFAULT_FRAME_STRIDE; gst_video_info_set_format (&(config->info), DEFAULT_FORMAT, DEFAULT_WIDTH, DEFAULT_HEIGHT); for (i = 0; i < GST_VIDEO_MAX_PLANES; ++i) { config->plane_offsets[i] = GST_VIDEO_INFO_PLANE_OFFSET (&(config->info), i); config->plane_strides[i] = GST_VIDEO_INFO_PLANE_STRIDE (&(config->info), i); } } static void gst_raw_video_parse_update_info (GstRawVideoParseConfig * config) { guint i; guint n_planes; guint last_plane; gsize last_plane_offset, last_plane_size; GstVideoInfo *info = &(config->info); GST_DEBUG ("updating info with width %u height %u format %s " " custom plane strides&offsets %d", config->width, config->height, gst_video_format_to_string (config->format), config->custom_plane_strides); gst_video_info_set_format (info, config->format, config->width, config->height); GST_VIDEO_INFO_PAR_N (info) = config->pixel_aspect_ratio_n; GST_VIDEO_INFO_PAR_D (info) = config->pixel_aspect_ratio_d; GST_VIDEO_INFO_FPS_N (info) = config->framerate_n; GST_VIDEO_INFO_FPS_D (info) = config->framerate_d; GST_VIDEO_INFO_INTERLACE_MODE (info) = config->interlaced ? GST_VIDEO_INTERLACE_MODE_INTERLEAVED : GST_VIDEO_INTERLACE_MODE_PROGRESSIVE; /* Check if there are custom plane strides & offsets that need to be preserved */ if (config->custom_plane_strides) { /* In case there are, overwrite the offsets&strides computed by * gst_video_info_set_format with the custom ones */ for (i = 0; i < GST_VIDEO_MAX_PLANES; ++i) { GST_VIDEO_INFO_PLANE_OFFSET (info, i) = config->plane_offsets[i]; GST_VIDEO_INFO_PLANE_STRIDE (info, i) = config->plane_strides[i]; } } else { /* No custom planes&offsets; copy the computed ones into * the plane_offsets & plane_strides arrays to ensure they * are equal to the ones in the videoinfo */ for (i = 0; i < GST_VIDEO_MAX_PLANES; ++i) { config->plane_offsets[i] = GST_VIDEO_INFO_PLANE_OFFSET (info, i); config->plane_strides[i] = GST_VIDEO_INFO_PLANE_STRIDE (info, i); } } n_planes = GST_VIDEO_INFO_N_PLANES (info); if (n_planes < 1) n_planes = 1; /* Figure out what plane is the physically last one. Typically, the * this is the last plane in the list (= at index n_planes-1). * However, this is not guaranteed, so we have to scan the offsets * to find the last plane. */ last_plane_offset = 0; last_plane = 0; for (i = 0; i < n_planes; ++i) { gsize plane_offset = GST_VIDEO_INFO_PLANE_OFFSET (info, i); if (plane_offset >= last_plane_offset) { last_plane = i; last_plane_offset = plane_offset; } } last_plane = n_planes - 1; last_plane_size = GST_VIDEO_INFO_PLANE_STRIDE (info, last_plane) * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (info->finfo, last_plane, config->height); GST_VIDEO_INFO_SIZE (info) = last_plane_offset + last_plane_size; GST_DEBUG ("last plane #%u: offset: %" G_GSIZE_FORMAT " size: %" G_GSIZE_FORMAT " => frame size minus extra padding: %" G_GSIZE_FORMAT, last_plane, last_plane_offset, last_plane_size, GST_VIDEO_INFO_SIZE (info)); }