videoparsers: Add vp9parse element

Adding vp9parse element to parse various stream information such as
resolution, profile, and so on. If upstream does not provide resolution and/or
profile, this would be useful for decodebin pipeline for autoplugging
suitable decoder element depending on template caps of each decoder element.

In addition, vp9parse element supports unpacking superframe into
single frame for decoders. The vp9 superframe is a frame which consists
of multiple frames (or superframe with one frame is allowed) followed by superframe
index block. Then unpacked each frame will be considered as normal frame
by decoder. The decision for unpacking will be done by downstream element's
"alignment" caps field, which can be "super-frame" or "frame".
If downstream specifies the "alignment" as "frame",
then vp9parse element will split an incoming superframe into single frames
and the superframe index (located at the end of the superframe) data
will be discarded by vp9parse element.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1041>
This commit is contained in:
Seungha Yang 2020-09-09 21:38:33 +09:00 committed by GStreamer Merge Bot
parent 47bbc997f8
commit 2b152eae69
8 changed files with 2321 additions and 0 deletions

View file

@ -221903,6 +221903,33 @@
}, },
"properties": {}, "properties": {},
"rank": "none" "rank": "none"
},
"vp9parse": {
"author": "Seungha Yang <seungha@centricular.com>",
"description": "Parses VP9 streams",
"hierarchy": [
"GstVp9Parse",
"GstBaseParse",
"GstElement",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"klass": "Codec/Parser/Converter/Video",
"long-name": "VP9 parser",
"pad-templates": {
"sink": {
"caps": "video/x-vp9:\n",
"direction": "sink",
"presence": "always"
},
"src": {
"caps": "video/x-vp9:\n parsed: true\n alignment: { (string)super-frame, (string)frame }\n",
"direction": "src",
"presence": "always"
}
},
"rank": "secondary"
} }
}, },
"filename": "gstvideoparsersbad", "filename": "gstvideoparsersbad",

View file

@ -0,0 +1,784 @@
/* GStreamer
* Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/codecparsers/gstvp9parser.h>
#include <gst/video/video.h>
#include "gstvp9parse.h"
#include <string.h>
GST_DEBUG_CATEGORY (vp9_parse_debug);
#define GST_CAT_DEFAULT vp9_parse_debug
typedef enum
{
GST_VP9_PARSE_ALIGN_NONE = 0,
GST_VP9_PARSE_ALIGN_SUPER_FRAME,
GST_VP9_PARSE_ALIGN_FRAME,
} GstVp9ParseAligment;
struct _GstVp9Parse
{
GstBaseParse parent;
/* parsed from the last keyframe */
gint width;
gint height;
gint subsampling_x;
gint subsampling_y;
GstVp9ColorSpace color_space;
GstVp9ColorRange color_range;
GstVP9Profile profile;
GstVp9BitDepth bit_depth;
GstVp9ParseAligment in_align;
GstVp9ParseAligment align;
GstVp9Parser *parser;
gboolean update_caps;
/* per frame status */
gboolean discont;
};
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-vp9"));
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-vp9, parsed = (boolean) true, "
"alignment=(string) { super-frame, frame }"));
#define parent_class gst_vp9_parse_parent_class
G_DEFINE_TYPE (GstVp9Parse, gst_vp9_parse, GST_TYPE_BASE_PARSE);
static gboolean gst_vp9_parse_start (GstBaseParse * parse);
static gboolean gst_vp9_parse_stop (GstBaseParse * parse);
static GstFlowReturn gst_vp9_parse_handle_frame (GstBaseParse * parse,
GstBaseParseFrame * frame, gint * skipsize);
static gboolean gst_vp9_parse_set_sink_caps (GstBaseParse * parse,
GstCaps * caps);
static GstCaps *gst_vp9_parse_get_sink_caps (GstBaseParse * parse,
GstCaps * filter);
static void gst_vp9_parse_update_src_caps (GstVp9Parse * self, GstCaps * caps);
static GstFlowReturn gst_vp9_parse_parse_frame (GstVp9Parse * self,
GstBaseParseFrame * frame, GstVp9FrameHdr * frame_hdr);
static void
gst_vp9_parse_class_init (GstVp9ParseClass * klass)
{
GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
parse_class->start = GST_DEBUG_FUNCPTR (gst_vp9_parse_start);
parse_class->stop = GST_DEBUG_FUNCPTR (gst_vp9_parse_stop);
parse_class->handle_frame = GST_DEBUG_FUNCPTR (gst_vp9_parse_handle_frame);
parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_vp9_parse_set_sink_caps);
parse_class->get_sink_caps = GST_DEBUG_FUNCPTR (gst_vp9_parse_get_sink_caps);
gst_element_class_add_static_pad_template (element_class, &srctemplate);
gst_element_class_add_static_pad_template (element_class, &sinktemplate);
gst_element_class_set_static_metadata (element_class, "VP9 parser",
"Codec/Parser/Converter/Video",
"Parses VP9 streams", "Seungha Yang <seungha@centricular.com>");
GST_DEBUG_CATEGORY_INIT (vp9_parse_debug, "vp9parse", 0, "vp9 parser");
}
static void
gst_vp9_parse_init (GstVp9Parse * self)
{
gst_base_parse_set_pts_interpolation (GST_BASE_PARSE (self), FALSE);
gst_base_parse_set_infer_ts (GST_BASE_PARSE (self), FALSE);
GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (self));
GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (self));
}
static void
gst_vp9_parse_reset (GstVp9Parse * self)
{
self->width = 0;
self->height = 0;
self->subsampling_x = -1;
self->subsampling_y = -1;
self->color_space = GST_VP9_CS_UNKNOWN;
self->color_range = GST_VP9_CR_LIMITED;
self->profile = GST_VP9_PROFILE_UNDEFINED;
self->bit_depth = (GstVp9BitDepth) 0;
}
static gboolean
gst_vp9_parse_start (GstBaseParse * parse)
{
GstVp9Parse *self = GST_VP9_PARSE (parse);
GST_DEBUG_OBJECT (self, "start");
self->parser = gst_vp9_parser_new ();
gst_vp9_parse_reset (self);
/* short frame header with one byte */
gst_base_parse_set_min_frame_size (parse, 1);
return TRUE;
}
static gboolean
gst_vp9_parse_stop (GstBaseParse * parse)
{
GstVp9Parse *self = GST_VP9_PARSE (parse);
GST_DEBUG_OBJECT (self, "stop");
g_clear_pointer (&self->parser, gst_vp9_parser_free);
return TRUE;
}
static const gchar *
gst_vp9_parse_profile_to_string (GstVP9Profile profile)
{
switch (profile) {
case GST_VP9_PROFILE_0:
return "0";
case GST_VP9_PROFILE_1:
return "1";
case GST_VP9_PROFILE_2:
return "2";
case GST_VP9_PROFILE_3:
return "3";
default:
break;
}
return NULL;
}
static GstVP9Profile
gst_vp9_parse_profile_from_string (const gchar * profile)
{
if (!profile)
return GST_VP9_PROFILE_UNDEFINED;
if (g_strcmp0 (profile, "0") == 0)
return GST_VP9_PROFILE_0;
else if (g_strcmp0 (profile, "1") == 0)
return GST_VP9_PROFILE_1;
else if (g_strcmp0 (profile, "2") == 0)
return GST_VP9_PROFILE_2;
else if (g_strcmp0 (profile, "3") == 0)
return GST_VP9_PROFILE_3;
return GST_VP9_PROFILE_UNDEFINED;
}
static const gchar *
gst_vp9_parse_alignment_to_string (GstVp9ParseAligment align)
{
switch (align) {
case GST_VP9_PARSE_ALIGN_SUPER_FRAME:
return "super-frame";
case GST_VP9_PARSE_ALIGN_FRAME:
return "frame";
default:
break;
}
return NULL;
}
static GstVp9ParseAligment
gst_vp9_parse_alignment_from_string (const gchar * align)
{
if (!align)
return GST_VP9_PARSE_ALIGN_NONE;
if (g_strcmp0 (align, "super-frame") == 0)
return GST_VP9_PARSE_ALIGN_SUPER_FRAME;
else if (g_strcmp0 (align, "frame") == 0)
return GST_VP9_PARSE_ALIGN_FRAME;
return GST_VP9_PARSE_ALIGN_NONE;
}
static void
gst_vp9_parse_alignment_from_caps (GstCaps * caps, GstVp9ParseAligment * align)
{
*align = GST_VP9_PARSE_ALIGN_NONE;
GST_DEBUG ("parsing caps: %" GST_PTR_FORMAT, caps);
if (caps && gst_caps_get_size (caps) > 0) {
GstStructure *s = gst_caps_get_structure (caps, 0);
const gchar *str = NULL;
if ((str = gst_structure_get_string (s, "alignment"))) {
*align = gst_vp9_parse_alignment_from_string (str);
}
}
}
/* check downstream caps to configure format and alignment */
static void
gst_vp9_parse_negotiate (GstVp9Parse * self, GstVp9ParseAligment in_align,
GstCaps * in_caps)
{
GstCaps *caps;
GstVp9ParseAligment align = self->align;
caps = gst_pad_get_allowed_caps (GST_BASE_PARSE_SRC_PAD (self));
GST_DEBUG_OBJECT (self, "allowed caps: %" GST_PTR_FORMAT, caps);
/* concentrate on leading structure, since decodebin parser
* capsfilter always includes parser template caps */
if (caps) {
caps = gst_caps_truncate (caps);
GST_DEBUG_OBJECT (self, "negotiating with caps: %" GST_PTR_FORMAT, caps);
}
if (in_caps && caps) {
if (gst_caps_can_intersect (in_caps, caps)) {
GST_DEBUG_OBJECT (self, "downstream accepts upstream caps");
gst_vp9_parse_alignment_from_caps (in_caps, &align);
gst_clear_caps (&caps);
}
}
/* FIXME We could fail the negotiation immediately if caps are empty */
if (caps && !gst_caps_is_empty (caps)) {
/* fixate to avoid ambiguity with lists when parsing */
caps = gst_caps_fixate (caps);
gst_vp9_parse_alignment_from_caps (caps, &align);
}
/* default */
if (align == GST_VP9_PARSE_ALIGN_NONE)
align = GST_VP9_PARSE_ALIGN_SUPER_FRAME;
GST_DEBUG_OBJECT (self, "selected alignment %s",
gst_vp9_parse_alignment_to_string (align));
self->align = align;
gst_clear_caps (&caps);
}
static gboolean
gst_vp9_parse_process_frame (GstVp9Parse * self, GstVp9FrameHdr * frame_hdr)
{
GstVp9Parser *parser = self->parser;
gint width, height;
/* the resolution might be varying. Update our status per key frame */
if (frame_hdr->frame_type != GST_VP9_KEY_FRAME) {
return TRUE;
}
width = frame_hdr->width;
height = frame_hdr->height;
if (frame_hdr->display_size_enabled &&
frame_hdr->display_width > 0 && frame_hdr->display_height) {
width = frame_hdr->display_width;
height = frame_hdr->display_height;
}
if (width != self->width || height != self->height) {
GST_DEBUG_OBJECT (self, "resolution change from %dx%d to %dx%d",
self->width, self->height, width, height);
self->width = width;
self->height = height;
self->update_caps = TRUE;
}
if (self->subsampling_x != parser->subsampling_x ||
self->subsampling_y != parser->subsampling_y) {
GST_DEBUG_OBJECT (self,
"subsampling changed from x: %d, y: %d to x: %d, y: %d",
self->subsampling_x, self->subsampling_y,
parser->subsampling_x, parser->subsampling_y);
self->subsampling_x = parser->subsampling_x;
self->subsampling_y = parser->subsampling_y;
self->update_caps = TRUE;
}
if (parser->color_space != GST_VP9_CS_UNKNOWN &&
parser->color_space != GST_VP9_CS_RESERVED_2 &&
parser->color_space != self->color_space) {
GST_DEBUG_OBJECT (self, "colorspace changed from %d to %d",
self->color_space, parser->color_space);
self->color_space = parser->color_space;
self->update_caps = TRUE;
}
if (parser->color_range != self->color_range) {
GST_DEBUG_OBJECT (self, "color range changed from %d to %d",
self->color_range, parser->color_range);
self->color_range = parser->color_range;
self->update_caps = TRUE;
}
if (frame_hdr->profile != GST_VP9_PROFILE_UNDEFINED &&
frame_hdr->profile != self->profile) {
GST_DEBUG_OBJECT (self, "profile changed from %d to %d", self->profile,
frame_hdr->profile);
self->profile = frame_hdr->profile;
self->update_caps = TRUE;
}
if (parser->bit_depth != self->bit_depth) {
GST_DEBUG_OBJECT (self, "bit-depth changed from %d to %d",
self->bit_depth, parser->bit_depth);
self->bit_depth = parser->bit_depth;
self->update_caps = TRUE;
}
return TRUE;
}
static GstFlowReturn
gst_vp9_parse_handle_frame (GstBaseParse * parse, GstBaseParseFrame * frame,
gint * skipsize)
{
GstVp9Parse *self = GST_VP9_PARSE (parse);
GstBuffer *buffer = frame->buffer;
GstFlowReturn ret = GST_FLOW_OK;
GstVp9ParserResult parse_res = GST_VP9_PARSER_ERROR;
GstMapInfo map;
gsize offset = 0;
GstVp9SuperframeInfo superframe_info;
guint i;
GstVp9FrameHdr frame_hdr;
if (GST_BUFFER_FLAG_IS_SET (frame->buffer, GST_BUFFER_FLAG_DISCONT))
self->discont = TRUE;
else
self->discont = FALSE;
/* need to save buffer from invalidation upon _finish_frame */
if (self->align == GST_VP9_PARSE_ALIGN_FRAME)
buffer = gst_buffer_copy (frame->buffer);
if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) {
GST_ELEMENT_ERROR (parse, CORE, NOT_IMPLEMENTED, (NULL),
("Couldn't map incoming buffer"));
return GST_FLOW_ERROR;
}
GST_TRACE_OBJECT (self, "processing buffer of size %" G_GSIZE_FORMAT,
map.size);
/* superframe_info will be zero initialized by GstVp9Parser */
parse_res = gst_vp9_parser_parse_superframe_info (self->parser,
&superframe_info, map.data, map.size);
if (parse_res != GST_VP9_PARSER_OK) {
/* just finish this frame anyway, so that we don't too strict
* regarding parsing vp9 stream.
* Downstream might be able to handle this stream even though
* it's very unlikely */
GST_WARNING_OBJECT (self, "Couldn't parse superframe res: %d", parse_res);
goto done;
}
for (i = 0; i < superframe_info.frames_in_superframe; i++) {
guint32 frame_size;
frame_size = superframe_info.frame_sizes[i];
parse_res = gst_vp9_parser_parse_frame_header (self->parser,
&frame_hdr, map.data + offset, frame_size);
if (parse_res != GST_VP9_PARSER_OK) {
GST_WARNING_OBJECT (self, "Parsing error %d", parse_res);
break;
}
gst_vp9_parse_process_frame (self, &frame_hdr);
if (self->align == GST_VP9_PARSE_ALIGN_FRAME) {
GstBaseParseFrame subframe;
gst_base_parse_frame_init (&subframe);
subframe.flags |= frame->flags;
subframe.offset = frame->offset;
subframe.overhead = frame->overhead;
subframe.buffer = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
offset, frame_size);
/* note we don't need to come up with a sub-buffer, since
* subsequent code only considers input buffer's metadata.
* Real data is either taken from input by baseclass or
* a replacement output buffer is provided anyway. */
gst_vp9_parse_parse_frame (self, &subframe, &frame_hdr);
ret = gst_base_parse_finish_frame (parse, &subframe, frame_size);
} else {
/* FIXME: need to parse all frames belong to this superframe? */
break;
}
offset += frame_size;
}
done:
gst_buffer_unmap (buffer, &map);
if (self->align != GST_VP9_PARSE_ALIGN_FRAME) {
if (parse_res == GST_VP9_PARSER_OK)
gst_vp9_parse_parse_frame (self, frame, &frame_hdr);
ret = gst_base_parse_finish_frame (parse, frame, map.size);
} else {
gst_buffer_unref (buffer);
if (offset != map.size) {
gsize left = map.size - offset;
if (left != superframe_info.superframe_index_size) {
GST_WARNING_OBJECT (parse,
"Skipping leftover frame data %" G_GSIZE_FORMAT, left);
}
frame->flags |= GST_BASE_PARSE_FRAME_FLAG_DROP;
ret = gst_base_parse_finish_frame (parse, frame, left);
}
}
return ret;
}
static void
gst_vp9_parse_update_src_caps (GstVp9Parse * self, GstCaps * caps)
{
GstCaps *sink_caps, *src_caps;
GstCaps *final_caps = NULL;
GstStructure *s = NULL;
gint width, height;
gint par_n = 0, par_d = 0;
gint fps_n = 0, fps_d = 0;
gint bitdepth = 0;
gchar *colorimetry = NULL;
const gchar *chroma_format = NULL;
const gchar *profile = NULL;
if (!self->update_caps)
return;
/* if this is being called from the first _setcaps call, caps on the sinkpad
* aren't set yet and so they need to be passed as an argument */
if (caps)
sink_caps = gst_caps_ref (caps);
else
sink_caps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (self));
/* carry over input caps as much as possible; override with our own stuff */
if (!sink_caps)
sink_caps = gst_caps_new_empty_simple ("video/x-vp9");
else
s = gst_caps_get_structure (sink_caps, 0);
final_caps = gst_caps_copy (sink_caps);
/* frame header should give this but upstream overrides */
if (s && gst_structure_has_field (s, "width") &&
gst_structure_has_field (s, "height")) {
gst_structure_get_int (s, "width", &width);
gst_structure_get_int (s, "height", &height);
} else {
width = self->width;
height = self->height;
}
if (width > 0 && height > 0)
gst_caps_set_simple (final_caps, "width", G_TYPE_INT, width,
"height", G_TYPE_INT, height, NULL);
if (s && gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d)) {
if (par_n != 0 && par_d != 0) {
gst_caps_set_simple (final_caps, "pixel-aspect-ratio",
GST_TYPE_FRACTION, par_n, par_d, NULL);
}
}
if (s && gst_structure_has_field (s, "framerate")) {
gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d);
}
if (fps_n > 0 && fps_d > 0) {
gst_caps_set_simple (final_caps, "framerate",
GST_TYPE_FRACTION, fps_n, fps_d, NULL);
gst_base_parse_set_frame_rate (GST_BASE_PARSE (self), fps_n, fps_d, 0, 0);
}
if (self->color_space != GST_VP9_CS_UNKNOWN &&
self->color_space != GST_VP9_CS_RESERVED_2) {
GstVideoColorimetry cinfo;
gboolean have_cinfo = TRUE;
memset (&cinfo, 0, sizeof (GstVideoColorimetry));
switch (self->parser->color_space) {
case GST_VP9_CS_BT_601:
gst_video_colorimetry_from_string (&cinfo, GST_VIDEO_COLORIMETRY_BT601);
break;
case GST_VP9_CS_BT_709:
gst_video_colorimetry_from_string (&cinfo, GST_VIDEO_COLORIMETRY_BT709);
break;
case GST_VP9_CS_SMPTE_170:
gst_video_colorimetry_from_string (&cinfo, GST_VIDEO_COLORIMETRY_BT601);
break;
case GST_VP9_CS_SMPTE_240:
gst_video_colorimetry_from_string (&cinfo,
GST_VIDEO_COLORIMETRY_SMPTE240M);
break;
case GST_VP9_CS_BT_2020:
if (self->parser->bit_depth == GST_VP9_BIT_DEPTH_12) {
gst_video_colorimetry_from_string (&cinfo,
GST_VIDEO_COLORIMETRY_BT2020);
} else {
gst_video_colorimetry_from_string (&cinfo,
GST_VIDEO_COLORIMETRY_BT2020_10);
}
break;
case GST_VP9_CS_SRGB:
gst_video_colorimetry_from_string (&cinfo, GST_VIDEO_COLORIMETRY_SRGB);
break;
default:
have_cinfo = FALSE;
break;
}
if (have_cinfo) {
if (self->parser->color_range == GST_VP9_CR_LIMITED)
cinfo.range = GST_VIDEO_COLOR_RANGE_16_235;
else
cinfo.range = GST_VIDEO_COLOR_RANGE_0_255;
colorimetry = gst_video_colorimetry_to_string (&cinfo);
}
}
if (self->color_space != GST_VP9_CS_SRGB) {
if (self->parser->subsampling_x == 1 && self->parser->subsampling_y == 1)
chroma_format = "4:2:0";
else if (self->parser->subsampling_x == 1 &&
self->parser->subsampling_y == 0)
chroma_format = "4:2:2";
else if (self->parser->subsampling_x == 0 &&
self->parser->subsampling_y == 1)
chroma_format = "4:4:0";
else if (self->parser->subsampling_x == 1 &&
self->parser->subsampling_y == 1)
chroma_format = "4:4:4";
if (chroma_format)
gst_caps_set_simple (final_caps,
"chroma-format", G_TYPE_STRING, chroma_format, NULL);
}
switch (self->bit_depth) {
case GST_VP9_BIT_DEPTH_8:
bitdepth = 8;
break;
case GST_VP9_BIT_DEPTH_10:
bitdepth = 10;
break;
case GST_VP9_BIT_DEPTH_12:
bitdepth = 12;
break;
default:
break;
}
if (bitdepth) {
gst_caps_set_simple (final_caps,
"bit-depth-luma", G_TYPE_UINT, bitdepth,
"bit-depth-chroma", G_TYPE_UINT, bitdepth, NULL);
}
if (colorimetry && (!s || !gst_structure_has_field (s, "colorimetry"))) {
gst_caps_set_simple (final_caps,
"colorimetry", G_TYPE_STRING, colorimetry, NULL);
}
g_free (colorimetry);
gst_caps_set_simple (final_caps, "parsed", G_TYPE_BOOLEAN, TRUE,
"alignment", G_TYPE_STRING,
gst_vp9_parse_alignment_to_string (self->align), NULL);
profile = gst_vp9_parse_profile_to_string (self->profile);
if (profile)
gst_caps_set_simple (final_caps, "profile", G_TYPE_STRING, profile, NULL);
src_caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (self));
if (!(src_caps && gst_caps_is_strictly_equal (src_caps, final_caps))) {
GST_DEBUG_OBJECT (self, "Update src caps %" GST_PTR_FORMAT, final_caps);
gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (self), final_caps);
}
gst_clear_caps (&src_caps);
gst_caps_unref (final_caps);
gst_caps_unref (sink_caps);
self->update_caps = FALSE;
}
static GstFlowReturn
gst_vp9_parse_parse_frame (GstVp9Parse * self, GstBaseParseFrame * frame,
GstVp9FrameHdr * frame_hdr)
{
GstBuffer *buffer;
buffer = frame->buffer;
gst_vp9_parse_update_src_caps (self, NULL);
if (frame_hdr->frame_type == GST_VP9_KEY_FRAME)
GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
else
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
if (self->align == GST_VP9_PARSE_ALIGN_FRAME) {
if (!frame_hdr->show_frame)
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DECODE_ONLY);
else
GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DECODE_ONLY);
}
if (self->discont) {
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
self->discont = FALSE;
}
return GST_FLOW_OK;
}
static gboolean
gst_vp9_parse_set_sink_caps (GstBaseParse * parse, GstCaps * caps)
{
GstVp9Parse *self = GST_VP9_PARSE (parse);
GstStructure *str;
GstVp9ParseAligment align;
GstCaps *in_caps = NULL;
const gchar *profile;
str = gst_caps_get_structure (caps, 0);
/* accept upstream info if provided */
gst_structure_get_int (str, "width", &self->width);
gst_structure_get_int (str, "height", &self->height);
profile = gst_structure_get_string (str, "profile");
if (profile)
self->profile = gst_vp9_parse_profile_from_string (profile);
/* get upstream align from caps */
gst_vp9_parse_alignment_from_caps (caps, &align);
/* default */
if (align == GST_VP9_PARSE_ALIGN_NONE)
align = GST_VP9_PARSE_ALIGN_SUPER_FRAME;
/* prefer alignment type determined above */
in_caps = gst_caps_copy (caps);
gst_caps_set_simple (in_caps, "alignment", G_TYPE_STRING,
gst_vp9_parse_alignment_to_string (align), NULL);
/* negotiate with downstream, set output align */
gst_vp9_parse_negotiate (self, align, in_caps);
self->update_caps = TRUE;
/* if all of decoder's capability related values are provided
* by upstream, update src caps now */
if (self->width > 0 && self->height > 0 && profile)
gst_vp9_parse_update_src_caps (self, in_caps);
gst_caps_unref (in_caps);
self->in_align = align;
return TRUE;
}
static void
remove_fields (GstCaps * caps, gboolean all)
{
guint i, n;
n = gst_caps_get_size (caps);
for (i = 0; i < n; i++) {
GstStructure *s = gst_caps_get_structure (caps, i);
if (all) {
gst_structure_remove_field (s, "alignment");
}
gst_structure_remove_field (s, "parsed");
}
}
static GstCaps *
gst_vp9_parse_get_sink_caps (GstBaseParse * parse, GstCaps * filter)
{
GstCaps *peercaps, *templ;
GstCaps *res, *tmp, *pcopy;
templ = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SINK_PAD (parse));
if (filter) {
GstCaps *fcopy = gst_caps_copy (filter);
/* Remove the fields we convert */
remove_fields (fcopy, TRUE);
peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), fcopy);
gst_caps_unref (fcopy);
} else {
peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), NULL);
}
pcopy = gst_caps_copy (peercaps);
remove_fields (pcopy, TRUE);
res = gst_caps_intersect_full (pcopy, templ, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (pcopy);
gst_caps_unref (templ);
if (filter) {
GstCaps *tmp = gst_caps_intersect_full (res, filter,
GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (res);
res = tmp;
}
/* Try if we can put the downstream caps first */
pcopy = gst_caps_copy (peercaps);
remove_fields (pcopy, FALSE);
tmp = gst_caps_intersect_full (pcopy, res, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (pcopy);
if (!gst_caps_is_empty (tmp))
res = gst_caps_merge (tmp, res);
else
gst_caps_unref (tmp);
gst_caps_unref (peercaps);
return res;
}

View file

@ -0,0 +1,34 @@
/* GStreamer
* Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
*
* 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.
*/
#ifndef __GST_VP9_PARSE_H__
#define __GST_VP9_PARSE_H__
#include <gst/gst.h>
#include <gst/base/gstbaseparse.h>
G_BEGIN_DECLS
#define GST_TYPE_VP9_PARSE (gst_vp9_parse_get_type())
G_DECLARE_FINAL_TYPE (GstVp9Parse,
gst_vp9_parse, GST, VP9_PARSE, GstBaseParse);
G_END_DECLS
#endif /* __GST_VP9_PARSE_H__ */

View file

@ -12,6 +12,7 @@ vparse_sources = [
'gsth265parse.c', 'gsth265parse.c',
'gstvideoparseutils.c', 'gstvideoparseutils.c',
'gstjpeg2000parse.c', 'gstjpeg2000parse.c',
'gstvp9parse.c',
] ]
gstvideoparsersbad = library('gstvideoparsersbad', gstvideoparsersbad = library('gstvideoparsersbad',

View file

@ -31,6 +31,7 @@
#include "gstjpeg2000parse.h" #include "gstjpeg2000parse.h"
#include "gstvc1parse.h" #include "gstvc1parse.h"
#include "gsth265parse.h" #include "gsth265parse.h"
#include "gstvp9parse.h"
GST_DEBUG_CATEGORY (videoparseutils_debug); GST_DEBUG_CATEGORY (videoparseutils_debug);
@ -61,6 +62,14 @@ plugin_init (GstPlugin * plugin)
ret |= gst_element_register (plugin, "vc1parse", ret |= gst_element_register (plugin, "vc1parse",
GST_RANK_NONE, GST_TYPE_VC1_PARSE); GST_RANK_NONE, GST_TYPE_VC1_PARSE);
/**
* element-vp9parse:
*
* Since: 1.20
*/
ret |= gst_element_register (plugin, "vp9parse",
GST_RANK_SECONDARY, GST_TYPE_VP9_PARSE);
return ret; return ret;
} }

View file

@ -0,0 +1,160 @@
/* GStreamer
* Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/check/gstcheck.h>
#include <gst/check/gstharness.h>
#include "vp9parse.h"
#include <string.h>
typedef struct
{
const guint8 *data;
guint len;
gboolean superframe;
guint subframe_len[2];
} GstVp9ParseTestFrameData;
static void
run_split_superframe_with_caps (const gchar * in_caps)
{
GstHarness *h;
GstBuffer *in_buf, *out_buf = NULL;
GstMapInfo map;
GstFlowReturn ret;
gint i = 0;
GstVp9ParseTestFrameData frames[] = {
{profile_0_frame0, profile_0_frame0_len, FALSE, {profile_0_frame0_len, 0}},
{profile_0_frame1, profile_0_frame1_len, TRUE, {profile_0_frame1_first_len,
profile_0_frame1_last_len}},
{profile_0_frame2, profile_0_frame2_len, FALSE, {profile_0_frame2_len, 0}},
};
h = gst_harness_new_parse ("vp9parse");
fail_unless (h != NULL);
gst_harness_set_sink_caps_str (h, "video/x-vp9,alignment=(string)frame");
/* default alignment is super-frame */
gst_harness_set_src_caps_str (h, in_caps);
gst_harness_play (h);
for (i = 0; i < G_N_ELEMENTS (frames); i++) {
in_buf = gst_buffer_new_and_alloc (frames[i].len);
gst_buffer_map (in_buf, &map, GST_MAP_WRITE);
memcpy (map.data, frames[i].data, frames[i].len);
gst_buffer_unmap (in_buf, &map);
ret = gst_harness_push (h, in_buf);
fail_unless (ret == GST_FLOW_OK, "GstFlowReturn was %s",
gst_flow_get_name (ret));
out_buf = gst_harness_try_pull (h);
fail_unless (out_buf);
fail_unless_equals_int (gst_buffer_get_size (out_buf),
frames[i].subframe_len[0]);
if (i == 0) {
GstEvent *event;
GstCaps *caps = NULL;
GstStructure *s;
const gchar *profile;
gint width, height;
fail_if (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DELTA_UNIT));
while ((event = gst_harness_try_pull_event (h))) {
GstCaps *event_caps;
if (GST_EVENT_TYPE (event) != GST_EVENT_CAPS) {
gst_event_unref (event);
continue;
}
gst_event_parse_caps (event, &event_caps);
gst_caps_replace (&caps, event_caps);
gst_event_unref (event);
}
fail_unless (caps != NULL);
s = gst_caps_get_structure (caps, 0);
fail_unless (gst_structure_get_int (s, "width", &width));
fail_unless (gst_structure_get_int (s, "height", &height));
fail_unless ((profile = gst_structure_get_string (s, "profile")));
fail_unless_equals_int (width, 256);
fail_unless_equals_int (height, 144);
fail_unless_equals_string (profile, "0");
gst_caps_unref (caps);
} else {
fail_unless (GST_BUFFER_FLAG_IS_SET (out_buf,
GST_BUFFER_FLAG_DELTA_UNIT));
}
if (frames[i].superframe) {
/* this is decoding only frame */
fail_unless (GST_BUFFER_FLAG_IS_SET (out_buf,
GST_BUFFER_FLAG_DECODE_ONLY));
fail_unless (GST_BUFFER_FLAG_IS_SET (out_buf,
GST_BUFFER_FLAG_DELTA_UNIT));
gst_clear_buffer (&out_buf);
out_buf = gst_harness_try_pull (h);
fail_unless (out_buf);
fail_unless_equals_int (gst_buffer_get_size (out_buf),
frames[i].subframe_len[1]);
fail_unless (GST_BUFFER_FLAG_IS_SET (out_buf,
GST_BUFFER_FLAG_DELTA_UNIT));
}
gst_clear_buffer (&out_buf);
}
gst_harness_teardown (h);
}
GST_START_TEST (test_split_superframe)
{
/* vp9parse will split frame if downstream alignment is frame
* whatever the upstream alignment was specified */
run_split_superframe_with_caps ("video/x-vp9");
run_split_superframe_with_caps ("video/x-vp9,alignment=(string)super-frame");
run_split_superframe_with_caps ("video/x-vp9,alignment=(string)frame");
}
GST_END_TEST;
static Suite *
vp9parse_suite (void)
{
Suite *s;
TCase *tc_chain;
s = suite_create ("vp9parse");
tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_split_superframe);
return s;
}
GST_CHECK_MAIN (vp9parse);

File diff suppressed because it is too large Load diff

View file

@ -55,6 +55,7 @@ base_tests = [
[['elements/switchbin.c']], [['elements/switchbin.c']],
[['elements/videoframe-audiolevel.c']], [['elements/videoframe-audiolevel.c']],
[['elements/viewfinderbin.c']], [['elements/viewfinderbin.c']],
[['elements/vp9parse.c'], false, [gstcodecparsers_dep]],
[['elements/wasapi2.c'], host_machine.system() != 'windows', ], [['elements/wasapi2.c'], host_machine.system() != 'windows', ],
[['libs/h264parser.c'], false, [gstcodecparsers_dep]], [['libs/h264parser.c'], false, [gstcodecparsers_dep]],
[['libs/h265parser.c'], false, [gstcodecparsers_dep]], [['libs/h265parser.c'], false, [gstcodecparsers_dep]],