/* GStreamer * Copyright (C) 2006 David A. Schleef * Copyright (C) 2007,2009 Sebastian Dröge * * gstvideoparse.c: * * 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-videoparse * * Converts a byte stream into video frames. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "gstvideoparse.h" static void gst_video_parse_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_video_parse_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstCaps *gst_video_parse_get_caps (GstRawParse * rp); static void gst_video_parse_pre_push_buffer (GstRawParse * rp, GstBuffer * buffer); static void gst_video_parse_decide_allocation (GstRawParse * rp, GstQuery * query); static void gst_video_parse_update_info (GstVideoParse * vp); static gboolean gst_video_parse_deserialize_int_array (const gchar * str, gint * dest, guint n_values); GST_DEBUG_CATEGORY_STATIC (gst_video_parse_debug); #define GST_CAT_DEFAULT gst_video_parse_debug enum { PROP_0, PROP_FORMAT, PROP_WIDTH, PROP_HEIGHT, PROP_PAR, PROP_FRAMERATE, PROP_INTERLACED, PROP_TOP_FIELD_FIRST, PROP_STRIDES, PROP_OFFSETS, PROP_FRAMESIZE }; #define gst_video_parse_parent_class parent_class G_DEFINE_TYPE (GstVideoParse, gst_video_parse, GST_TYPE_RAW_PARSE); static void gst_video_parse_class_init (GstVideoParseClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); GstRawParseClass *rp_class = GST_RAW_PARSE_CLASS (klass); GstCaps *caps; gobject_class->set_property = gst_video_parse_set_property; gobject_class->get_property = gst_video_parse_get_property; rp_class->get_caps = gst_video_parse_get_caps; rp_class->pre_push_buffer = gst_video_parse_pre_push_buffer; rp_class->decide_allocation = gst_video_parse_decide_allocation; g_object_class_install_property (gobject_class, PROP_FORMAT, g_param_spec_enum ("format", "Format", "Format of images in raw stream", GST_TYPE_VIDEO_FORMAT, GST_VIDEO_FORMAT_I420, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_WIDTH, g_param_spec_int ("width", "Width", "Width of images in raw stream", 0, INT_MAX, 320, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_HEIGHT, g_param_spec_int ("height", "Height", "Height of images in raw stream", 0, INT_MAX, 240, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_FRAMERATE, gst_param_spec_fraction ("framerate", "Frame Rate", "Frame rate of images in raw stream", 0, 1, G_MAXINT, 1, 25, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PAR, gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio", "Pixel aspect ratio of images in raw stream", 1, 100, 100, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_INTERLACED, g_param_spec_boolean ("interlaced", "Interlaced flag", "True if video is interlaced", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_TOP_FIELD_FIRST, g_param_spec_boolean ("top-field-first", "Top field first", "True if top field is earlier than bottom field", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_STRIDES, g_param_spec_string ("strides", "Strides", "Stride of each planes in bytes using string format: 's0,s1,s2,s3'", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_OFFSETS, g_param_spec_string ("offsets", "Offsets", "Offset of each planes in bytes using string format: 'o0,o1,o2,o3'", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_FRAMESIZE, g_param_spec_uint ("framesize", "Framesize", "Size of an image in raw stream (0: default)", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gst_element_class_set_static_metadata (gstelement_class, "Video Parse", "Filter/Video", "Converts stream into video frames", "David Schleef , " "Sebastian Dröge "); caps = gst_caps_from_string ("video/x-raw; video/x-bayer"); gst_raw_parse_class_set_src_pad_template (rp_class, caps); gst_raw_parse_class_set_multiple_frames_per_buffer (rp_class, FALSE); gst_caps_unref (caps); GST_DEBUG_CATEGORY_INIT (gst_video_parse_debug, "videoparse", 0, "videoparse element"); } static void gst_video_parse_init (GstVideoParse * vp) { vp->width = 320; vp->height = 240; vp->format = GST_VIDEO_FORMAT_I420; vp->par_n = 1; vp->par_d = 1; gst_raw_parse_set_fps (GST_RAW_PARSE (vp), 25, 1); gst_video_parse_update_info (vp); } static void gst_video_parse_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstVideoParse *vp = GST_VIDEO_PARSE (object); g_return_if_fail (!gst_raw_parse_is_negotiated (GST_RAW_PARSE (vp))); switch (prop_id) { case PROP_FORMAT: vp->format = g_value_get_enum (value); break; case PROP_WIDTH: vp->width = g_value_get_int (value); break; case PROP_HEIGHT: vp->height = g_value_get_int (value); break; case PROP_FRAMERATE: gst_raw_parse_set_fps (GST_RAW_PARSE (vp), gst_value_get_fraction_numerator (value), gst_value_get_fraction_denominator (value)); break; case PROP_PAR: vp->par_n = gst_value_get_fraction_numerator (value); vp->par_d = gst_value_get_fraction_denominator (value); break; case PROP_INTERLACED: vp->interlaced = g_value_get_boolean (value); break; case PROP_TOP_FIELD_FIRST: vp->top_field_first = g_value_get_boolean (value); break; case PROP_STRIDES: if (gst_video_parse_deserialize_int_array (g_value_get_string (value), vp->stride, GST_VIDEO_MAX_PLANES)) { vp->stride_set = TRUE; } else { GST_WARNING_OBJECT (vp, "failed to deserialize given strides"); vp->stride_set = FALSE; } break; case PROP_OFFSETS: if (gst_video_parse_deserialize_int_array (g_value_get_string (value), vp->offset, GST_VIDEO_MAX_PLANES)) { vp->offset_set = TRUE; } else { GST_WARNING_OBJECT (vp, "failed to deserialized given offsets"); vp->offset_set = FALSE; } break; case PROP_FRAMESIZE: vp->framesize = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } gst_video_parse_update_info (vp); } static void gst_video_parse_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstVideoParse *vp = GST_VIDEO_PARSE (object); switch (prop_id) { case PROP_FORMAT: g_value_set_enum (value, vp->format); break; case PROP_WIDTH: g_value_set_int (value, vp->width); break; case PROP_HEIGHT: g_value_set_int (value, vp->height); break; case PROP_FRAMERATE:{ gint fps_n, fps_d; gst_raw_parse_get_fps (GST_RAW_PARSE (vp), &fps_n, &fps_d); gst_value_set_fraction (value, fps_n, fps_d); break; } case PROP_PAR: gst_value_set_fraction (value, vp->par_n, vp->par_d); break; case PROP_INTERLACED: g_value_set_boolean (value, vp->interlaced); break; case PROP_TOP_FIELD_FIRST: g_value_set_boolean (value, vp->top_field_first); break; case PROP_STRIDES: { gchar *tmp; tmp = g_strdup_printf ("%d,%d,%d,%d", vp->info.stride[0], vp->info.stride[1], vp->info.stride[2], vp->info.stride[3]); g_value_set_string (value, tmp); g_free (tmp); break; } case PROP_OFFSETS: { gchar *tmp; tmp = g_strdup_printf ("%" G_GSIZE_FORMAT ",%" G_GSIZE_FORMAT ",%" G_GSIZE_FORMAT ",%" G_GSIZE_FORMAT, vp->info.offset[0], vp->info.offset[1], vp->info.offset[2], vp->info.offset[3]); g_value_set_string (value, tmp); g_free (tmp); break; } case PROP_FRAMESIZE: g_value_set_uint (value, vp->info.size); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gst_video_parse_deserialize_int_array (const gchar * str, gint * dest, guint n_values) { gchar **strv; guint length; guint i; strv = g_strsplit (str, ",", n_values); if (strv == NULL) return FALSE; length = g_strv_length (strv); for (i = 0; i < length; i++) { gint64 val; val = g_ascii_strtoll (strv[i], NULL, 10); if (val < G_MININT || val > G_MAXINT) { g_strfreev (strv); return FALSE; } dest[i] = val; } /* fill remaining values with 0 */ for (i = length; i < n_values; i++) dest[i] = 0; g_strfreev (strv); return TRUE; } static inline gsize gst_video_parse_get_plane_size (GstVideoInfo * info, guint plane) { gsize size = 0; if (GST_VIDEO_FORMAT_INFO_IS_TILED (info->finfo)) { gint tile_width, tile_height, x_tiles, y_tiles; tile_width = 1 << GST_VIDEO_FORMAT_INFO_TILE_WS (info->finfo); tile_height = 1 << GST_VIDEO_FORMAT_INFO_TILE_HS (info->finfo); x_tiles = GST_VIDEO_TILE_X_TILES (info->stride[plane]); y_tiles = GST_VIDEO_TILE_Y_TILES (info->stride[plane]); /* plane size is the size of one tile multiplied by the number of tiles */ size = tile_width * tile_height * x_tiles * y_tiles; } else { size = info->stride[plane] * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (info->finfo, plane, info->height); } return size; } static gboolean gst_video_parse_update_stride (GstVideoParse * vp) { GstVideoInfo *info = &vp->info; guint i; /* 1. check that provided strides are greater than the default ones */ for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) { if (GST_VIDEO_FORMAT_INFO_IS_TILED (info->finfo)) { /* for tiled format, make sure there is more tiles than default */ gint default_x_tiles, default_y_tiles, x_tiles, y_tiles; x_tiles = GST_VIDEO_TILE_X_TILES (vp->stride[i]); y_tiles = GST_VIDEO_TILE_Y_TILES (vp->stride[i]); default_x_tiles = GST_VIDEO_TILE_X_TILES (info->stride[i]); default_y_tiles = GST_VIDEO_TILE_Y_TILES (info->stride[i]); if (x_tiles < default_x_tiles) { GST_WARNING_OBJECT (vp, "x_tiles for plane %u is too small: got %d, min %d", i, x_tiles, default_x_tiles); return FALSE; } if (y_tiles < default_y_tiles) { GST_WARNING_OBJECT (vp, "y_tiles for plane %u is too small: got %d, min %d", i, y_tiles, default_y_tiles); return FALSE; } } else { if (vp->stride[i] < info->stride[i]) { GST_WARNING_OBJECT (vp, "stride for plane %u is too small: got %d, min %d", i, vp->stride[i], info->stride[i]); return FALSE; } } } /* 2. update stride and plane offsets */ for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) { if (vp->stride[i] != info->stride[i]) { info->stride[i] = vp->stride[i]; if (i > 0) { /* update offset to reflect stride change for plane > 0 */ info->offset[i] = info->offset[i - 1] + gst_video_parse_get_plane_size (info, i - 1); } vp->need_videometa = TRUE; } } return TRUE; } static gboolean gst_video_parse_update_offset (GstVideoParse * vp) { GstVideoInfo *info = &vp->info; guint i; /* 1. check that provided offsets are greaters than the default ones and are * consistent with plane size */ for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) { gsize min_offset = info->offset[i]; if (i > 0) min_offset = MAX (min_offset, vp->offset[i - 1] + gst_video_parse_get_plane_size (info, i - 1)); if (vp->offset[i] < min_offset) { GST_WARNING_OBJECT (vp, "offset for plane %u is too small: got %d, min %" G_GSIZE_FORMAT, i, vp->offset[i], min_offset); return FALSE; } } /* 2. update offsets */ for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) { if (vp->offset[i] != info->offset[i]) { info->offset[i] = vp->offset[i]; vp->need_videometa = TRUE; } } return TRUE; } static void gst_video_parse_update_info (GstVideoParse * vp) { GstVideoInfo *info = &vp->info; gint fps_n, fps_d; gint framesize; guint i; gboolean update_size = FALSE; gst_raw_parse_get_fps (GST_RAW_PARSE (vp), &fps_n, &fps_d); gst_video_info_init (info); gst_video_info_set_format (info, vp->format, vp->width, vp->height); info->fps_n = fps_n; info->fps_d = fps_d; info->par_n = vp->par_n; info->par_d = vp->par_d; info->interlace_mode = vp->interlaced ? GST_VIDEO_INTERLACE_MODE_INTERLEAVED : GST_VIDEO_INTERLACE_MODE_PROGRESSIVE; vp->need_videometa = FALSE; if (vp->stride_set) { if (gst_video_parse_update_stride (vp)) update_size = TRUE; else GST_WARNING_OBJECT (vp, "invalid strides set, use default ones"); } if (vp->offset_set) { if (gst_video_parse_update_offset (vp)) update_size = TRUE; else GST_WARNING_OBJECT (vp, "invalid offsets set, use default ones"); } if (update_size) { framesize = 0; for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) { gint planesize = info->offset[i]; planesize += gst_video_parse_get_plane_size (info, i); if (planesize > framesize) framesize = planesize; } info->size = framesize; } if (vp->framesize) { /* user requires a specific framesize, just make sure it's bigger than * the current one */ if (vp->framesize > vp->info.size) vp->info.size = vp->framesize; else GST_WARNING_OBJECT (vp, "invalid framesize set: got %u, min: %" G_GSIZE_FORMAT, vp->framesize, vp->info.size); } GST_DEBUG_OBJECT (vp, "video info: %ux%u, format %s, size %" G_GSIZE_FORMAT ", stride {%d,%d,%d,%d}, offset {%" G_GSIZE_FORMAT ",%" G_GSIZE_FORMAT ",%" G_GSIZE_FORMAT ",%" G_GSIZE_FORMAT "}", GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_NAME (info), GST_VIDEO_INFO_SIZE (info), info->stride[0], info->stride[1], info->stride[2], info->stride[3], info->offset[0], info->offset[1], info->offset[2], info->offset[3]); /* update base class framesize */ framesize = GST_VIDEO_INFO_SIZE (info); gst_raw_parse_set_framesize (GST_RAW_PARSE (vp), framesize); } static GstCaps * gst_video_parse_get_caps (GstRawParse * rp) { GstVideoParse *vp = GST_VIDEO_PARSE (rp); return gst_video_info_to_caps (&vp->info); } static gboolean gst_video_parse_copy_frame (GstVideoParse * vp, GstBuffer * dest, GstVideoInfo * dest_info, GstBuffer * src, GstVideoInfo * src_info) { GstVideoFrame src_frame; GstVideoFrame dest_frame; gboolean ret; if (!gst_video_frame_map (&src_frame, src_info, src, GST_MAP_READ)) { GST_ERROR_OBJECT (vp, "failed to map src frame"); return FALSE; } if (!gst_video_frame_map (&dest_frame, dest_info, dest, GST_MAP_WRITE)) { GST_ERROR_OBJECT (vp, "failed to map dest frame"); gst_video_frame_unmap (&src_frame); return FALSE; } ret = gst_video_frame_copy (&dest_frame, &src_frame); gst_video_frame_unmap (&src_frame); gst_video_frame_unmap (&dest_frame); return ret; } static void gst_video_parse_pre_push_buffer (GstRawParse * rp, GstBuffer * buffer) { GstVideoParse *vp = GST_VIDEO_PARSE (rp); if (vp->do_copy) { GstVideoInfo info; GstBuffer *outbuf; gst_video_info_init (&info); gst_video_info_set_format (&info, vp->format, vp->width, vp->height); GST_DEBUG_OBJECT (vp, "copying frame to remove padding"); outbuf = gst_buffer_new_allocate (NULL, GST_VIDEO_INFO_SIZE (&info), NULL); if (!gst_video_parse_copy_frame (vp, outbuf, &info, buffer, &vp->info)) GST_WARNING_OBJECT (vp, "failed to copy frame"); gst_buffer_replace_all_memory (buffer, gst_buffer_get_all_memory (outbuf)); gst_buffer_unref (outbuf); } else { GstVideoInfo *info = &vp->info; GstVideoFrameFlags flags = GST_VIDEO_FRAME_FLAG_NONE; if (vp->interlaced && vp->top_field_first) flags = GST_VIDEO_FRAME_FLAG_TFF; gst_buffer_add_video_meta_full (buffer, flags, GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_N_PLANES (info), info->offset, info->stride); } if (vp->interlaced) { if (vp->top_field_first) { GST_BUFFER_FLAG_SET (buffer, GST_VIDEO_BUFFER_FLAG_TFF); } else { GST_BUFFER_FLAG_UNSET (buffer, GST_VIDEO_BUFFER_FLAG_TFF); } } } static void gst_video_parse_decide_allocation (GstRawParse * rp, GstQuery * query) { GstVideoParse *vp = GST_VIDEO_PARSE (rp); gboolean has_videometa; has_videometa = gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); /* no need to copy if downstream supports videometa or if we don't need * them */ if (has_videometa || !vp->need_videometa) return; vp->do_copy = TRUE; }