gstreamer/gst/rawparse/gstvideoparse.c
Nicolas Dufresne acb7205f91 videoparse: Fix framesize calculation
When the framesize is not specified, we try and calculate a size from
the strides and offset information. This was done with the sum of
offsets + the size of the last frame. That is just wrong method. We also
need to account for video meta that may be flipping two planes. An
example is if you convert I420 to YV12 by flipping the two last offsets.

https://bugzilla.gnome.org/show_bug.cgi?id=760270
2016-01-28 14:48:11 -05:00

597 lines
18 KiB
C

/* GStreamer
* Copyright (C) 2006 David A. Schleef <ds@schleef.org>
* Copyright (C) 2007,2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* 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 <ds@schleef.org>, "
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
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;
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;
}