/* GStreamer * Copyright (C) <1999> Erik Walthinsen * Library <2002> Ronald Bultje * Copyright (C) 2007 David A. Schleef * * 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:video-info * @title: GstVideoInfo * @short_description: Structures and enumerations to describe raw images */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "video-info.h" #include "video-tile.h" #ifndef GST_DISABLE_GST_DEBUG #define GST_CAT_DEFAULT ensure_debug_category() static GstDebugCategory * ensure_debug_category (void) { static gsize cat_gonce = 0; if (g_once_init_enter (&cat_gonce)) { gsize cat_done; cat_done = (gsize) _gst_debug_category_new ("video-info", 0, "video-info structure"); g_once_init_leave (&cat_gonce, cat_done); } return (GstDebugCategory *) cat_gonce; } #else #define ensure_debug_category() /* NOOP */ #endif /* GST_DISABLE_GST_DEBUG */ /** * gst_video_info_copy: * @info: a #GstVideoInfo * * Copy a GstVideoInfo structure. * * Returns: a new #GstVideoInfo. free with gst_video_info_free. * * Since: 1.6 */ GstVideoInfo * gst_video_info_copy (const GstVideoInfo * info) { return g_slice_dup (GstVideoInfo, info); } /** * gst_video_info_free: * @info: a #GstVideoInfo * * Free a GstVideoInfo structure previously allocated with gst_video_info_new() * or gst_video_info_copy(). * * Since: 1.6 */ void gst_video_info_free (GstVideoInfo * info) { g_slice_free (GstVideoInfo, info); } G_DEFINE_BOXED_TYPE (GstVideoInfo, gst_video_info, (GBoxedCopyFunc) gst_video_info_copy, (GBoxedFreeFunc) gst_video_info_free); /** * gst_video_info_new: * * Allocate a new #GstVideoInfo that is also initialized with * gst_video_info_init(). * * Returns: a new #GstVideoInfo. free with gst_video_info_free(). * * Since: 1.6 */ GstVideoInfo * gst_video_info_new (void) { GstVideoInfo *info; info = g_slice_new (GstVideoInfo); gst_video_info_init (info); return info; } static gboolean fill_planes (GstVideoInfo * info); /** * gst_video_info_init: * @info: a #GstVideoInfo * * Initialize @info with default values. */ void gst_video_info_init (GstVideoInfo * info) { g_return_if_fail (info != NULL); memset (info, 0, sizeof (GstVideoInfo)); info->finfo = gst_video_format_get_info (GST_VIDEO_FORMAT_UNKNOWN); info->views = 1; /* arrange for sensible defaults, e.g. if turned into caps */ info->fps_n = 0; info->fps_d = 1; info->par_n = 1; info->par_d = 1; GST_VIDEO_INFO_MULTIVIEW_MODE (info) = GST_VIDEO_MULTIVIEW_MODE_NONE; GST_VIDEO_INFO_MULTIVIEW_FLAGS (info) = GST_VIDEO_MULTIVIEW_FLAGS_NONE; GST_VIDEO_INFO_FIELD_ORDER (info) = GST_VIDEO_FIELD_ORDER_UNKNOWN; } #define MAKE_COLORIMETRY(r,m,t,p) { \ GST_VIDEO_COLOR_RANGE ##r, GST_VIDEO_COLOR_MATRIX_ ##m, \ GST_VIDEO_TRANSFER_ ##t, GST_VIDEO_COLOR_PRIMARIES_ ##p } #define DEFAULT_YUV_SD 0 #define DEFAULT_YUV_HD 1 #define DEFAULT_RGB 2 #define DEFAULT_GRAY 3 #define DEFAULT_UNKNOWN 4 #define DEFAULT_YUV_UHD 5 static const GstVideoColorimetry default_color[] = { MAKE_COLORIMETRY (_16_235, BT601, BT709, SMPTE170M), MAKE_COLORIMETRY (_16_235, BT709, BT709, BT709), MAKE_COLORIMETRY (_0_255, RGB, SRGB, BT709), MAKE_COLORIMETRY (_0_255, BT601, UNKNOWN, UNKNOWN), MAKE_COLORIMETRY (_UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN), MAKE_COLORIMETRY (_16_235, BT2020, BT2020_12, BT2020), }; static void set_default_colorimetry (GstVideoInfo * info) { const GstVideoFormatInfo *finfo = info->finfo; if (GST_VIDEO_FORMAT_INFO_IS_YUV (finfo)) { if (info->height >= 2160) { info->chroma_site = GST_VIDEO_CHROMA_SITE_H_COSITED; info->colorimetry = default_color[DEFAULT_YUV_UHD]; } else if (info->height > 576) { info->chroma_site = GST_VIDEO_CHROMA_SITE_H_COSITED; info->colorimetry = default_color[DEFAULT_YUV_HD]; } else { info->chroma_site = GST_VIDEO_CHROMA_SITE_NONE; info->colorimetry = default_color[DEFAULT_YUV_SD]; } } else if (GST_VIDEO_FORMAT_INFO_IS_GRAY (finfo)) { info->colorimetry = default_color[DEFAULT_GRAY]; } else if (GST_VIDEO_FORMAT_INFO_IS_RGB (finfo)) { info->colorimetry = default_color[DEFAULT_RGB]; } else { info->colorimetry = default_color[DEFAULT_UNKNOWN]; } } static gboolean validate_colorimetry (GstVideoInfo * info) { const GstVideoFormatInfo *finfo = info->finfo; if (!GST_VIDEO_FORMAT_INFO_IS_RGB (finfo) && info->colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_RGB) { GST_WARNING ("color matrix RGB is only supported with RGB format, %s is not", finfo->name); return FALSE; } if (GST_VIDEO_FORMAT_INFO_IS_YUV (finfo) && info->colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_UNKNOWN) { GST_WARNING ("Need to specify a color matrix when using YUV format (%s)", finfo->name); return FALSE; } return TRUE; } static gboolean gst_video_info_set_format_common (GstVideoInfo * info, GstVideoFormat format, guint width, guint height) { g_return_val_if_fail (info != NULL, FALSE); g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, FALSE); if (width > G_MAXINT || height > G_MAXINT) return FALSE; gst_video_info_init (info); info->finfo = gst_video_format_get_info (format); info->width = width; info->height = height; info->views = 1; set_default_colorimetry (info); return TRUE; } /** * gst_video_info_set_format: * @info: a #GstVideoInfo * @format: the format * @width: a width * @height: a height * * Set the default info for a video frame of @format and @width and @height. * * Note: This initializes @info first, no values are preserved. This function * does not set the offsets correctly for interlaced vertically * subsampled formats. * * Returns: %FALSE if the returned video info is invalid, e.g. because the * size of a frame can't be represented as a 32 bit integer (Since: 1.12) */ gboolean gst_video_info_set_format (GstVideoInfo * info, GstVideoFormat format, guint width, guint height) { if (!gst_video_info_set_format_common (info, format, width, height)) return FALSE; return fill_planes (info); } /** * gst_video_info_set_interlaced_format: * @info: a #GstVideoInfo * @format: the format * @mode: a #GstVideoInterlaceMode * @width: a width * @height: a height * * Same as #gst_video_info_set_format but also allowing to set the interlaced * mode. * * Returns: %FALSE if the returned video info is invalid, e.g. because the * size of a frame can't be represented as a 32 bit integer. * * Since: 1.16 */ gboolean gst_video_info_set_interlaced_format (GstVideoInfo * info, GstVideoFormat format, GstVideoInterlaceMode mode, guint width, guint height) { if (!gst_video_info_set_format_common (info, format, width, height)) return FALSE; GST_VIDEO_INFO_INTERLACE_MODE (info) = mode; return fill_planes (info); } static const gchar *interlace_mode[] = { "progressive", "interleaved", "mixed", "fields", "alternate" }; /** * gst_video_interlace_mode_to_string: * @mode: a #GstVideoInterlaceMode * * Convert @mode to its string representation. * * Returns: @mode as a string or NULL if @mode in invalid. * * Since: 1.6 */ const gchar * gst_video_interlace_mode_to_string (GstVideoInterlaceMode mode) { if (((guint) mode) >= G_N_ELEMENTS (interlace_mode)) return NULL; return interlace_mode[mode]; } /** * gst_video_interlace_mode_from_string: * @mode: a mode * * Convert @mode to a #GstVideoInterlaceMode * * Returns: the #GstVideoInterlaceMode of @mode or * #GST_VIDEO_INTERLACE_MODE_PROGRESSIVE when @mode is not a valid * string representation for a #GstVideoInterlaceMode. * * Since: 1.6 */ GstVideoInterlaceMode gst_video_interlace_mode_from_string (const gchar * mode) { gint i; for (i = 0; i < G_N_ELEMENTS (interlace_mode); i++) { if (g_str_equal (interlace_mode[i], mode)) return i; } return GST_VIDEO_INTERLACE_MODE_PROGRESSIVE; } static const gchar *field_order[] = { "unknown", "top-field-first", "bottom-field-first" }; /** * gst_video_field_order_to_string: * @order: a #GstVideoFieldOrder * * Convert @order to its string representation. * * Returns: @order as a string or NULL if @order in invalid. * * Since: 1.12 */ const gchar * gst_video_field_order_to_string (GstVideoFieldOrder order) { if (((guint) order) >= G_N_ELEMENTS (field_order)) return NULL; return field_order[order]; } /** * gst_video_field_order_from_string: * @order: a field order * * Convert @order to a #GstVideoFieldOrder * * Returns: the #GstVideoFieldOrder of @order or * #GST_VIDEO_FIELD_ORDER_UNKNOWN when @order is not a valid * string representation for a #GstVideoFieldOrder. * * Since: 1.12 */ GstVideoFieldOrder gst_video_field_order_from_string (const gchar * order) { gint i; for (i = 0; i < G_N_ELEMENTS (field_order); i++) { if (g_str_equal (field_order[i], order)) return i; } return GST_VIDEO_FIELD_ORDER_UNKNOWN; } /** * gst_video_info_from_caps: * @info: a #GstVideoInfo * @caps: a #GstCaps * * Parse @caps and update @info. * * Returns: TRUE if @caps could be parsed */ gboolean gst_video_info_from_caps (GstVideoInfo * info, const GstCaps * caps) { GstStructure *structure; const gchar *s; GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN; gint width = 0, height = 0; gint fps_n, fps_d; gint par_n, par_d; guint multiview_flags; g_return_val_if_fail (info != NULL, FALSE); g_return_val_if_fail (caps != NULL, FALSE); g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); GST_DEBUG ("parsing caps %" GST_PTR_FORMAT, caps); structure = gst_caps_get_structure (caps, 0); if (gst_structure_has_name (structure, "video/x-raw")) { if (!(s = gst_structure_get_string (structure, "format"))) goto no_format; format = gst_video_format_from_string (s); if (format == GST_VIDEO_FORMAT_UNKNOWN) goto unknown_format; } else if (g_str_has_prefix (gst_structure_get_name (structure), "video/") || g_str_has_prefix (gst_structure_get_name (structure), "image/")) { format = GST_VIDEO_FORMAT_ENCODED; } else { goto wrong_name; } /* width and height are mandatory, except for non-raw-formats */ if (!gst_structure_get_int (structure, "width", &width) && format != GST_VIDEO_FORMAT_ENCODED) goto no_width; if (!gst_structure_get_int (structure, "height", &height) && format != GST_VIDEO_FORMAT_ENCODED) goto no_height; gst_video_info_init (info); info->finfo = gst_video_format_get_info (format); info->width = width; info->height = height; if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)) { if (fps_n == 0) { /* variable framerate */ info->flags |= GST_VIDEO_FLAG_VARIABLE_FPS; /* see if we have a max-framerate */ gst_structure_get_fraction (structure, "max-framerate", &fps_n, &fps_d); } info->fps_n = fps_n; info->fps_d = fps_d; } else { /* unspecified is variable framerate */ info->fps_n = 0; info->fps_d = 1; } if (gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_n, &par_d)) { info->par_n = par_n; info->par_d = par_d; } else { info->par_n = 1; info->par_d = 1; } if ((s = gst_structure_get_string (structure, "interlace-mode"))) info->interlace_mode = gst_video_interlace_mode_from_string (s); else info->interlace_mode = GST_VIDEO_INTERLACE_MODE_PROGRESSIVE; /* Interlaced feature is mandatory for raw alternate streams */ if (info->interlace_mode == GST_VIDEO_INTERLACE_MODE_ALTERNATE && format != GST_VIDEO_FORMAT_ENCODED) { GstCapsFeatures *f; f = gst_caps_get_features (caps, 0); if (!f || !gst_caps_features_contains (f, GST_CAPS_FEATURE_FORMAT_INTERLACED)) goto alternate_no_feature; } if (GST_VIDEO_INFO_IS_INTERLACED (info) && (s = gst_structure_get_string (structure, "field-order"))) { GST_VIDEO_INFO_FIELD_ORDER (info) = gst_video_field_order_from_string (s); } else { GST_VIDEO_INFO_FIELD_ORDER (info) = GST_VIDEO_FIELD_ORDER_UNKNOWN; } { if ((s = gst_structure_get_string (structure, "multiview-mode"))) GST_VIDEO_INFO_MULTIVIEW_MODE (info) = gst_video_multiview_mode_from_caps_string (s); else GST_VIDEO_INFO_MULTIVIEW_MODE (info) = GST_VIDEO_MULTIVIEW_MODE_NONE; if (gst_structure_get_flagset (structure, "multiview-flags", &multiview_flags, NULL)) GST_VIDEO_INFO_MULTIVIEW_FLAGS (info) = multiview_flags; if (!gst_structure_get_int (structure, "views", &info->views)) info->views = 1; /* At one point, I tried normalising the half-aspect flag here, * but it behaves weird for GstVideoInfo operations other than * directly converting to/from caps - sometimes causing the * PAR to be doubled/halved too many times */ } if ((s = gst_structure_get_string (structure, "chroma-site"))) info->chroma_site = gst_video_chroma_from_string (s); else info->chroma_site = GST_VIDEO_CHROMA_SITE_UNKNOWN; if ((s = gst_structure_get_string (structure, "colorimetry"))) { if (!gst_video_colorimetry_from_string (&info->colorimetry, s)) { GST_WARNING ("unparsable colorimetry, using default"); set_default_colorimetry (info); } else if (!validate_colorimetry (info)) { GST_WARNING ("invalid colorimetry, using default"); set_default_colorimetry (info); } else { /* force RGB matrix for RGB formats */ if (GST_VIDEO_FORMAT_INFO_IS_RGB (info->finfo) && info->colorimetry.matrix != GST_VIDEO_COLOR_MATRIX_RGB) { GST_WARNING ("invalid matrix %d for RGB format, using RGB", info->colorimetry.matrix); info->colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB; } } } else { GST_DEBUG ("no colorimetry, using default"); set_default_colorimetry (info); } if (!fill_planes (info)) return FALSE; return TRUE; /* ERROR */ wrong_name: { GST_ERROR ("wrong name '%s', expected video/ or image/", gst_structure_get_name (structure)); return FALSE; } no_format: { GST_ERROR ("no format given"); return FALSE; } unknown_format: { GST_ERROR ("unknown format '%s' given", s); return FALSE; } no_width: { GST_ERROR ("no width property given"); return FALSE; } no_height: { GST_ERROR ("no height property given"); return FALSE; } alternate_no_feature: { GST_ERROR ("caps has 'interlace-mode=alternate' but doesn't have the Interlaced feature"); return FALSE; } } /** * gst_video_info_is_equal: * @info: a #GstVideoInfo * @other: a #GstVideoInfo * * Compares two #GstVideoInfo and returns whether they are equal or not * * Returns: %TRUE if @info and @other are equal, else %FALSE. */ gboolean gst_video_info_is_equal (const GstVideoInfo * info, const GstVideoInfo * other) { gint i; if (GST_VIDEO_INFO_FORMAT (info) != GST_VIDEO_INFO_FORMAT (other)) return FALSE; if (GST_VIDEO_INFO_INTERLACE_MODE (info) != GST_VIDEO_INFO_INTERLACE_MODE (other)) return FALSE; if (GST_VIDEO_INFO_FLAGS (info) != GST_VIDEO_INFO_FLAGS (other)) return FALSE; if (GST_VIDEO_INFO_WIDTH (info) != GST_VIDEO_INFO_WIDTH (other)) return FALSE; if (GST_VIDEO_INFO_HEIGHT (info) != GST_VIDEO_INFO_HEIGHT (other)) return FALSE; if (GST_VIDEO_INFO_SIZE (info) != GST_VIDEO_INFO_SIZE (other)) return FALSE; if (GST_VIDEO_INFO_PAR_N (info) != GST_VIDEO_INFO_PAR_N (other)) return FALSE; if (GST_VIDEO_INFO_PAR_D (info) != GST_VIDEO_INFO_PAR_D (other)) return FALSE; if (GST_VIDEO_INFO_FPS_N (info) != GST_VIDEO_INFO_FPS_N (other)) return FALSE; if (GST_VIDEO_INFO_FPS_D (info) != GST_VIDEO_INFO_FPS_D (other)) return FALSE; if (!gst_video_colorimetry_is_equal (&GST_VIDEO_INFO_COLORIMETRY (info), &GST_VIDEO_INFO_COLORIMETRY (other))) return FALSE; if (GST_VIDEO_INFO_CHROMA_SITE (info) != GST_VIDEO_INFO_CHROMA_SITE (other)) return FALSE; if (GST_VIDEO_INFO_MULTIVIEW_MODE (info) != GST_VIDEO_INFO_MULTIVIEW_MODE (other)) return FALSE; if (GST_VIDEO_INFO_MULTIVIEW_FLAGS (info) != GST_VIDEO_INFO_MULTIVIEW_FLAGS (other)) return FALSE; if (GST_VIDEO_INFO_VIEWS (info) != GST_VIDEO_INFO_VIEWS (other)) return FALSE; for (i = 0; i < info->finfo->n_planes; i++) { if (info->stride[i] != other->stride[i]) return FALSE; if (info->offset[i] != other->offset[i]) return FALSE; } return TRUE; } /** * gst_video_info_to_caps: * @info: a #GstVideoInfo * * Convert the values of @info into a #GstCaps. * * Returns: a new #GstCaps containing the info of @info. */ GstCaps * gst_video_info_to_caps (GstVideoInfo * info) { GstCaps *caps; const gchar *format; gchar *color; gint par_n, par_d; GstVideoColorimetry colorimetry; g_return_val_if_fail (info != NULL, NULL); g_return_val_if_fail (info->finfo != NULL, NULL); g_return_val_if_fail (info->finfo->format != GST_VIDEO_FORMAT_UNKNOWN, NULL); format = gst_video_format_to_string (info->finfo->format); g_return_val_if_fail (format != NULL, NULL); caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, format, "width", G_TYPE_INT, info->width, "height", G_TYPE_INT, info->height, NULL); par_n = info->par_n; par_d = info->par_d; gst_caps_set_simple (caps, "interlace-mode", G_TYPE_STRING, gst_video_interlace_mode_to_string (info->interlace_mode), NULL); if ((info->interlace_mode == GST_VIDEO_INTERLACE_MODE_INTERLEAVED || info->interlace_mode == GST_VIDEO_INTERLACE_MODE_ALTERNATE) && GST_VIDEO_INFO_FIELD_ORDER (info) != GST_VIDEO_FIELD_ORDER_UNKNOWN) { gst_caps_set_simple (caps, "field-order", G_TYPE_STRING, gst_video_field_order_to_string (GST_VIDEO_INFO_FIELD_ORDER (info)), NULL); } if (info->interlace_mode == GST_VIDEO_INTERLACE_MODE_ALTERNATE) { /* 'alternate' mode must always be accompanied by interlaced caps feature. */ GstCapsFeatures *features; features = gst_caps_features_new (GST_CAPS_FEATURE_FORMAT_INTERLACED, NULL); gst_caps_set_features (caps, 0, features); } if (GST_VIDEO_INFO_MULTIVIEW_MODE (info) != GST_VIDEO_MULTIVIEW_MODE_NONE) { const gchar *caps_str = NULL; /* If the half-aspect flag is set, applying it into the PAR of the * resulting caps now seems safe, and helps with automatic behaviour * in elements that aren't explicitly multiview aware */ if (GST_VIDEO_INFO_MULTIVIEW_FLAGS (info) & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) { GST_VIDEO_INFO_MULTIVIEW_FLAGS (info) &= ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT; switch (GST_VIDEO_INFO_MULTIVIEW_MODE (info)) { case GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE: case GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX: case GST_VIDEO_MULTIVIEW_MODE_COLUMN_INTERLEAVED: case GST_VIDEO_MULTIVIEW_MODE_CHECKERBOARD: par_n *= 2; /* double the width / half the height */ break; case GST_VIDEO_MULTIVIEW_MODE_ROW_INTERLEAVED: case GST_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM: par_d *= 2; /* half the width / double the height */ break; default: break; } } caps_str = gst_video_multiview_mode_to_caps_string (GST_VIDEO_INFO_MULTIVIEW_MODE (info)); if (caps_str != NULL) { gst_caps_set_simple (caps, "multiview-mode", G_TYPE_STRING, caps_str, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, GST_VIDEO_INFO_MULTIVIEW_FLAGS (info), GST_FLAG_SET_MASK_EXACT, NULL); } } gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION, par_n, par_d, NULL); if (info->chroma_site != GST_VIDEO_CHROMA_SITE_UNKNOWN) gst_caps_set_simple (caps, "chroma-site", G_TYPE_STRING, gst_video_chroma_to_string (info->chroma_site), NULL); /* make sure we set the RGB matrix for RGB formats */ colorimetry = info->colorimetry; if (GST_VIDEO_FORMAT_INFO_IS_RGB (info->finfo) && colorimetry.matrix != GST_VIDEO_COLOR_MATRIX_RGB) { GST_WARNING ("invalid matrix %d for RGB format, using RGB", colorimetry.matrix); colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB; } if ((color = gst_video_colorimetry_to_string (&colorimetry))) { gst_caps_set_simple (caps, "colorimetry", G_TYPE_STRING, color, NULL); g_free (color); } if (info->views > 1) gst_caps_set_simple (caps, "views", G_TYPE_INT, info->views, NULL); if (info->flags & GST_VIDEO_FLAG_VARIABLE_FPS && info->fps_n != 0) { /* variable fps with a max-framerate */ gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, 0, 1, "max-framerate", GST_TYPE_FRACTION, info->fps_n, info->fps_d, NULL); } else { /* no variable fps or no max-framerate */ gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, info->fps_n, info->fps_d, NULL); } return caps; } static gboolean fill_planes (GstVideoInfo * info) { gsize width, height, cr_h; gint bpp = 0, i; width = (gsize) info->width; height = (gsize) GST_VIDEO_INFO_FIELD_HEIGHT (info); /* Sanity check the resulting frame size for overflows */ for (i = 0; i < GST_VIDEO_INFO_N_COMPONENTS (info); i++) bpp += GST_VIDEO_INFO_COMP_DEPTH (info, i); bpp = GST_ROUND_UP_8 (bpp) / 8; if (bpp > 0 && GST_ROUND_UP_128 ((guint64) width) * ((guint64) height) >= G_MAXUINT / bpp) { GST_ERROR ("Frame size %ux%u would overflow", info->width, info->height); return FALSE; } switch (info->finfo->format) { case GST_VIDEO_FORMAT_YUY2: case GST_VIDEO_FORMAT_YVYU: case GST_VIDEO_FORMAT_UYVY: case GST_VIDEO_FORMAT_VYUY: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_AYUV: case GST_VIDEO_FORMAT_RGBx: case GST_VIDEO_FORMAT_RGBA: case GST_VIDEO_FORMAT_BGRx: case GST_VIDEO_FORMAT_BGRA: case GST_VIDEO_FORMAT_xRGB: case GST_VIDEO_FORMAT_ARGB: case GST_VIDEO_FORMAT_xBGR: case GST_VIDEO_FORMAT_ABGR: case GST_VIDEO_FORMAT_r210: case GST_VIDEO_FORMAT_Y410: case GST_VIDEO_FORMAT_VUYA: case GST_VIDEO_FORMAT_BGR10A2_LE: case GST_VIDEO_FORMAT_RGB10A2_LE: info->stride[0] = width * 4; info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_RGB16: case GST_VIDEO_FORMAT_BGR16: case GST_VIDEO_FORMAT_RGB15: case GST_VIDEO_FORMAT_BGR15: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_RGB: case GST_VIDEO_FORMAT_BGR: case GST_VIDEO_FORMAT_v308: case GST_VIDEO_FORMAT_IYU2: info->stride[0] = GST_ROUND_UP_4 (width * 3); info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_v210: info->stride[0] = ((width + 47) / 48) * 128; info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_v216: case GST_VIDEO_FORMAT_Y210: info->stride[0] = GST_ROUND_UP_8 (width * 4); info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_GRAY8: info->stride[0] = GST_ROUND_UP_4 (width); info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_GRAY16_BE: case GST_VIDEO_FORMAT_GRAY16_LE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_UYVP: info->stride[0] = GST_ROUND_UP_4 ((width * 2 * 5 + 3) / 4); info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_RGB8P: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = 4; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->size = info->offset[1] + (4 * 256); break; case GST_VIDEO_FORMAT_IYU1: info->stride[0] = GST_ROUND_UP_4 (GST_ROUND_UP_4 (width) + GST_ROUND_UP_4 (width) / 2); info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_ARGB64: case GST_VIDEO_FORMAT_AYUV64: info->stride[0] = width * 8; info->offset[0] = 0; info->size = info->stride[0] * height; break; case GST_VIDEO_FORMAT_I420: case GST_VIDEO_FORMAT_YV12: /* same as I420, but plane 1+2 swapped */ info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = GST_ROUND_UP_4 (GST_ROUND_UP_2 (width) / 2); info->stride[2] = info->stride[1]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); cr_h = GST_ROUND_UP_2 (height) / 2; if (GST_VIDEO_INFO_IS_INTERLACED (info)) cr_h = GST_ROUND_UP_2 (cr_h); info->offset[2] = info->offset[1] + info->stride[1] * cr_h; info->size = info->offset[2] + info->stride[2] * cr_h; break; case GST_VIDEO_FORMAT_Y41B: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = GST_ROUND_UP_16 (width) / 4; info->stride[2] = info->stride[1]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->offset[2] = info->offset[1] + info->stride[1] * height; /* simplification of ROUNDUP4(w)*h + 2*((ROUNDUP16(w)/4)*h */ info->size = (info->stride[0] + (GST_ROUND_UP_16 (width) / 2)) * height; break; case GST_VIDEO_FORMAT_Y42B: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = GST_ROUND_UP_8 (width) / 2; info->stride[2] = info->stride[1]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->offset[2] = info->offset[1] + info->stride[1] * height; /* simplification of ROUNDUP4(w)*h + 2*(ROUNDUP8(w)/2)*h */ info->size = (info->stride[0] + GST_ROUND_UP_8 (width)) * height; break; case GST_VIDEO_FORMAT_Y444: case GST_VIDEO_FORMAT_GBR: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = info->stride[0]; info->stride[2] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->offset[2] = info->offset[1] * 2; info->size = info->stride[0] * height * 3; break; case GST_VIDEO_FORMAT_GBRA: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = info->stride[0]; info->stride[2] = info->stride[0]; info->stride[3] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->offset[2] = info->offset[1] * 2; info->offset[3] = info->offset[1] * 3; info->size = info->stride[0] * height * 4; break; case GST_VIDEO_FORMAT_NV12: case GST_VIDEO_FORMAT_NV21: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); cr_h = GST_ROUND_UP_2 (height) / 2; if (GST_VIDEO_INFO_IS_INTERLACED (info)) cr_h = GST_ROUND_UP_2 (cr_h); info->size = info->offset[1] + info->stride[0] * cr_h; break; case GST_VIDEO_FORMAT_NV16: case GST_VIDEO_FORMAT_NV61: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->size = info->stride[0] * height * 2; break; case GST_VIDEO_FORMAT_NV24: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = GST_ROUND_UP_4 (width * 2); info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->size = info->stride[0] * height + info->stride[1] * height; break; case GST_VIDEO_FORMAT_A420: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = GST_ROUND_UP_4 (GST_ROUND_UP_2 (width) / 2); info->stride[2] = info->stride[1]; info->stride[3] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); cr_h = GST_ROUND_UP_2 (height) / 2; if (GST_VIDEO_INFO_IS_INTERLACED (info)) cr_h = GST_ROUND_UP_2 (cr_h); info->offset[2] = info->offset[1] + info->stride[1] * cr_h; info->offset[3] = info->offset[2] + info->stride[2] * cr_h; info->size = info->offset[3] + info->stride[0] * GST_ROUND_UP_2 (height); break; case GST_VIDEO_FORMAT_YUV9: case GST_VIDEO_FORMAT_YVU9: info->stride[0] = GST_ROUND_UP_4 (width); info->stride[1] = GST_ROUND_UP_4 (GST_ROUND_UP_4 (width) / 4); info->stride[2] = info->stride[1]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; cr_h = GST_ROUND_UP_4 (height) / 4; if (GST_VIDEO_INFO_IS_INTERLACED (info)) cr_h = GST_ROUND_UP_2 (cr_h); info->offset[2] = info->offset[1] + info->stride[1] * cr_h; info->size = info->offset[2] + info->stride[2] * cr_h; break; case GST_VIDEO_FORMAT_I420_10LE: case GST_VIDEO_FORMAT_I420_10BE: case GST_VIDEO_FORMAT_I420_12LE: case GST_VIDEO_FORMAT_I420_12BE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->stride[1] = GST_ROUND_UP_4 (width); info->stride[2] = info->stride[1]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); cr_h = GST_ROUND_UP_2 (height) / 2; if (GST_VIDEO_INFO_IS_INTERLACED (info)) cr_h = GST_ROUND_UP_2 (cr_h); info->offset[2] = info->offset[1] + info->stride[1] * cr_h; info->size = info->offset[2] + info->stride[2] * cr_h; break; case GST_VIDEO_FORMAT_I422_10LE: case GST_VIDEO_FORMAT_I422_10BE: case GST_VIDEO_FORMAT_I422_12LE: case GST_VIDEO_FORMAT_I422_12BE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->stride[1] = GST_ROUND_UP_4 (width); info->stride[2] = info->stride[1]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); info->offset[2] = info->offset[1] + info->stride[1] * GST_ROUND_UP_2 (height); info->size = info->offset[2] + info->stride[2] * GST_ROUND_UP_2 (height); break; case GST_VIDEO_FORMAT_Y444_10LE: case GST_VIDEO_FORMAT_Y444_10BE: case GST_VIDEO_FORMAT_Y444_12LE: case GST_VIDEO_FORMAT_Y444_12BE: case GST_VIDEO_FORMAT_GBR_10LE: case GST_VIDEO_FORMAT_GBR_10BE: case GST_VIDEO_FORMAT_GBR_12LE: case GST_VIDEO_FORMAT_GBR_12BE: case GST_VIDEO_FORMAT_Y444_16LE: case GST_VIDEO_FORMAT_Y444_16BE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->stride[1] = info->stride[0]; info->stride[2] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->offset[2] = info->offset[1] * 2; info->size = info->stride[0] * height * 3; break; case GST_VIDEO_FORMAT_GBRA_10LE: case GST_VIDEO_FORMAT_GBRA_10BE: case GST_VIDEO_FORMAT_GBRA_12LE: case GST_VIDEO_FORMAT_GBRA_12BE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->stride[1] = info->stride[0]; info->stride[2] = info->stride[0]; info->stride[3] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->offset[2] = info->offset[1] * 2; info->offset[3] = info->offset[1] * 3; info->size = info->stride[0] * height * 4; break; case GST_VIDEO_FORMAT_NV12_64Z32: info->stride[0] = GST_VIDEO_TILE_MAKE_STRIDE (GST_ROUND_UP_128 (width) / 64, GST_ROUND_UP_32 (height) / 32); info->stride[1] = GST_VIDEO_TILE_MAKE_STRIDE (GST_ROUND_UP_128 (width) / 64, GST_ROUND_UP_64 (height) / 64); info->offset[0] = 0; info->offset[1] = GST_ROUND_UP_128 (width) * GST_ROUND_UP_32 (height); info->size = info->offset[1] + GST_ROUND_UP_128 (width) * GST_ROUND_UP_64 (height) / 2; break; case GST_VIDEO_FORMAT_A420_10LE: case GST_VIDEO_FORMAT_A420_10BE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->stride[1] = GST_ROUND_UP_4 (width); info->stride[2] = info->stride[1]; info->stride[3] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); cr_h = GST_ROUND_UP_2 (height) / 2; if (GST_VIDEO_INFO_IS_INTERLACED (info)) cr_h = GST_ROUND_UP_2 (cr_h); info->offset[2] = info->offset[1] + info->stride[1] * cr_h; info->offset[3] = info->offset[2] + info->stride[2] * cr_h; info->size = info->offset[3] + info->stride[0] * GST_ROUND_UP_2 (height); break; case GST_VIDEO_FORMAT_A422_10LE: case GST_VIDEO_FORMAT_A422_10BE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->stride[1] = GST_ROUND_UP_4 (width); info->stride[2] = info->stride[1]; info->stride[3] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); info->offset[2] = info->offset[1] + info->stride[1] * GST_ROUND_UP_2 (height); info->offset[3] = info->offset[2] + info->stride[2] * GST_ROUND_UP_2 (height); info->size = info->offset[3] + info->stride[0] * GST_ROUND_UP_2 (height); break; case GST_VIDEO_FORMAT_A444_10LE: case GST_VIDEO_FORMAT_A444_10BE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->stride[1] = info->stride[0]; info->stride[2] = info->stride[0]; info->stride[3] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->offset[2] = info->offset[1] * 2; info->offset[3] = info->offset[1] * 3; info->size = info->stride[0] * height * 4; break; case GST_VIDEO_FORMAT_P010_10LE: case GST_VIDEO_FORMAT_P010_10BE: case GST_VIDEO_FORMAT_P016_LE: case GST_VIDEO_FORMAT_P016_BE: info->stride[0] = GST_ROUND_UP_4 (width * 2); info->stride[1] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); cr_h = GST_ROUND_UP_2 (height) / 2; info->size = info->offset[1] + info->stride[0] * cr_h; break; case GST_VIDEO_FORMAT_GRAY10_LE32: info->stride[0] = (width + 2) / 3 * 4; info->offset[0] = 0; info->size = info->stride[0] * GST_ROUND_UP_2 (height); break; case GST_VIDEO_FORMAT_NV12_10LE32: info->stride[0] = (width + 2) / 3 * 4; info->stride[1] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); cr_h = GST_ROUND_UP_2 (height) / 2; if (GST_VIDEO_INFO_IS_INTERLACED (info)) cr_h = GST_ROUND_UP_2 (cr_h); info->size = info->offset[1] + info->stride[0] * cr_h; break; case GST_VIDEO_FORMAT_NV16_10LE32: info->stride[0] = (width + 2) / 3 * 4; info->stride[1] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * height; info->size = info->stride[0] * height * 2; break; case GST_VIDEO_FORMAT_NV12_10LE40: info->stride[0] = ((width * 5 >> 2) + 4) / 5 * 5; info->stride[1] = info->stride[0]; info->offset[0] = 0; info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); cr_h = GST_ROUND_UP_2 (height) / 2; if (GST_VIDEO_INFO_IS_INTERLACED (info)) cr_h = GST_ROUND_UP_2 (cr_h); info->size = info->offset[1] + info->stride[0] * cr_h; break; case GST_VIDEO_FORMAT_ENCODED: break; case GST_VIDEO_FORMAT_UNKNOWN: GST_ERROR ("invalid format"); g_warning ("invalid format"); return FALSE; break; } return TRUE; } /** * gst_video_info_convert: * @info: a #GstVideoInfo * @src_format: #GstFormat of the @src_value * @src_value: value to convert * @dest_format: #GstFormat of the @dest_value * @dest_value: (out): pointer to destination value * * Converts among various #GstFormat types. This function handles * GST_FORMAT_BYTES, GST_FORMAT_TIME, and GST_FORMAT_DEFAULT. For * raw video, GST_FORMAT_DEFAULT corresponds to video frames. This * function can be used to handle pad queries of the type GST_QUERY_CONVERT. * * Returns: TRUE if the conversion was successful. */ gboolean gst_video_info_convert (GstVideoInfo * info, GstFormat src_format, gint64 src_value, GstFormat dest_format, gint64 * dest_value) { gboolean ret = FALSE; int fps_n, fps_d; gsize size; g_return_val_if_fail (info != NULL, 0); g_return_val_if_fail (info->finfo != NULL, 0); g_return_val_if_fail (info->finfo->format != GST_VIDEO_FORMAT_UNKNOWN, 0); g_return_val_if_fail (info->size > 0, 0); size = info->size; fps_n = info->fps_n; fps_d = info->fps_d; GST_DEBUG ("converting value %" G_GINT64_FORMAT " from %s to %s", src_value, gst_format_get_name (src_format), gst_format_get_name (dest_format)); if (src_format == dest_format) { *dest_value = src_value; ret = TRUE; goto done; } if (src_value == -1) { *dest_value = -1; ret = TRUE; goto done; } /* bytes to frames */ if (src_format == GST_FORMAT_BYTES && dest_format == GST_FORMAT_DEFAULT) { if (size != 0) { *dest_value = gst_util_uint64_scale (src_value, 1, size); } else { GST_ERROR ("blocksize is 0"); *dest_value = 0; } ret = TRUE; goto done; } /* frames to bytes */ if (src_format == GST_FORMAT_DEFAULT && dest_format == GST_FORMAT_BYTES) { *dest_value = gst_util_uint64_scale (src_value, size, 1); ret = TRUE; goto done; } /* time to frames */ if (src_format == GST_FORMAT_TIME && dest_format == GST_FORMAT_DEFAULT) { if (fps_d != 0) { *dest_value = gst_util_uint64_scale (src_value, fps_n, GST_SECOND * fps_d); } else { GST_ERROR ("framerate denominator is 0"); *dest_value = 0; } ret = TRUE; goto done; } /* frames to time */ if (src_format == GST_FORMAT_DEFAULT && dest_format == GST_FORMAT_TIME) { if (fps_n != 0) { *dest_value = gst_util_uint64_scale (src_value, GST_SECOND * fps_d, fps_n); } else { GST_ERROR ("framerate numerator is 0"); *dest_value = 0; } ret = TRUE; goto done; } /* time to bytes */ if (src_format == GST_FORMAT_TIME && dest_format == GST_FORMAT_BYTES) { if (fps_d != 0) { *dest_value = gst_util_uint64_scale (src_value, fps_n * size, GST_SECOND * fps_d); } else { GST_ERROR ("framerate denominator is 0"); *dest_value = 0; } ret = TRUE; goto done; } /* bytes to time */ if (src_format == GST_FORMAT_BYTES && dest_format == GST_FORMAT_TIME) { if (fps_n != 0 && size != 0) { *dest_value = gst_util_uint64_scale (src_value, GST_SECOND * fps_d, fps_n * size); } else { GST_ERROR ("framerate denominator and/or blocksize is 0"); *dest_value = 0; } ret = TRUE; } done: GST_DEBUG ("ret=%d result %" G_GINT64_FORMAT, ret, *dest_value); return ret; } /** * gst_video_info_align: * @info: a #GstVideoInfo * @align: alignment parameters * * Adjust the offset and stride fields in @info so that the padding and * stride alignment in @align is respected. * * Extra padding will be added to the right side when stride alignment padding * is required and @align will be updated with the new padding values. * * Returns: %FALSE if alignment could not be applied, e.g. because the * size of a frame can't be represented as a 32 bit integer (Since: 1.12) */ gboolean gst_video_info_align (GstVideoInfo * info, GstVideoAlignment * align) { const GstVideoFormatInfo *vinfo = info->finfo; gint width, height; gint padded_width, padded_height; gint i, n_planes; gboolean aligned; width = GST_VIDEO_INFO_WIDTH (info); height = GST_VIDEO_INFO_HEIGHT (info); GST_LOG ("padding %u-%ux%u-%u", align->padding_top, align->padding_left, align->padding_right, align->padding_bottom); n_planes = GST_VIDEO_INFO_N_PLANES (info); if (GST_VIDEO_FORMAT_INFO_HAS_PALETTE (vinfo)) n_planes--; /* first make sure the left padding does not cause alignment problems later */ do { GST_LOG ("left padding %u", align->padding_left); aligned = TRUE; for (i = 0; i < n_planes; i++) { gint hedge; /* this is the amout of pixels to add as left padding */ hedge = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (vinfo, i, align->padding_left); hedge *= GST_VIDEO_FORMAT_INFO_PSTRIDE (vinfo, i); GST_LOG ("plane %d, padding %d, alignment %u", i, hedge, align->stride_align[i]); aligned &= (hedge & align->stride_align[i]) == 0; } if (aligned) break; GST_LOG ("unaligned padding, increasing padding"); /* increase padded_width */ align->padding_left += align->padding_left & ~(align->padding_left - 1); } while (!aligned); /* add the padding */ padded_width = width + align->padding_left + align->padding_right; padded_height = height + align->padding_top + align->padding_bottom; do { GST_LOG ("padded dimension %u-%u", padded_width, padded_height); info->width = padded_width; info->height = padded_height; if (!fill_planes (info)) return FALSE; /* check alignment */ aligned = TRUE; for (i = 0; i < n_planes; i++) { GST_LOG ("plane %d, stride %d, alignment %u", i, info->stride[i], align->stride_align[i]); aligned &= (info->stride[i] & align->stride_align[i]) == 0; } if (aligned) break; GST_LOG ("unaligned strides, increasing dimension"); /* increase padded_width */ padded_width += padded_width & ~(padded_width - 1); } while (!aligned); align->padding_right = padded_width - width - align->padding_left; info->width = width; info->height = height; for (i = 0; i < n_planes; i++) { gint vedge, hedge, comp; /* Find the component for this plane, FIXME, we assume the plane number and * component number is the same for now, for scaling the dimensions this is * currently true for all formats but it might not be when adding new * formats. We might need to add a plane subsamling in the format info to * make this more generic or maybe use a plane -> component mapping. */ comp = i; hedge = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (vinfo, comp, align->padding_left); vedge = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (vinfo, comp, align->padding_top); GST_DEBUG ("plane %d: comp: %d, hedge %d vedge %d align %d stride %d", i, comp, hedge, vedge, align->stride_align[i], info->stride[i]); info->offset[i] += (vedge * info->stride[i]) + (hedge * GST_VIDEO_FORMAT_INFO_PSTRIDE (vinfo, comp)); } return TRUE; }