mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-08 13:18:52 +00:00
Decoders that required frame aligmment and didn't have an associated alpha decoder were skipped. This is because the parser was constructing caps based on the software alpha decoder, which specify super-frame alignment. Iterate over the caps to filter the one that have a matching codec-alpha, with the semantic the no codec-alpha field means codec-alpha=false. Then if everything was removed, callback to the original, so that the first non-alpha decoder will be picked. Fixes #820 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1855>
843 lines
24 KiB
C
843 lines
24 KiB
C
/* 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 "gstvideoparserselements.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;
|
|
gboolean codec_alpha;
|
|
|
|
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);
|
|
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (vp9parse, "vp9parse", GST_RANK_SECONDARY,
|
|
GST_TYPE_VP9_PARSE, videoparsers_element_init (plugin));
|
|
|
|
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;
|
|
self->codec_alpha = FALSE;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* implement custom semantic for codec-alpha */
|
|
static gboolean
|
|
gst_vp9_parse_check_codec_alpha (GstStructure * s, gboolean codec_alpha)
|
|
{
|
|
gboolean value;
|
|
|
|
if (gst_structure_get_boolean (s, "codec-alpha", &value))
|
|
return value == codec_alpha;
|
|
|
|
return codec_alpha == FALSE;
|
|
}
|
|
|
|
/* 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) {
|
|
while (gst_caps_get_size (caps) > 0) {
|
|
GstStructure *s = gst_caps_get_structure (caps, 0);
|
|
|
|
if (gst_vp9_parse_check_codec_alpha (s, self->codec_alpha))
|
|
break;
|
|
|
|
gst_caps_remove_structure (caps, 0);
|
|
}
|
|
|
|
/* this may happen if there is simply no codec alpha decoder in the
|
|
* gstreamer installation, in this case, pick the first non-alpha decoder.
|
|
*/
|
|
if (gst_caps_is_empty (caps)) {
|
|
gst_caps_unref (caps);
|
|
caps = gst_pad_get_allowed_caps (GST_BASE_PARSE_SRC_PAD (self));
|
|
}
|
|
|
|
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_is_info_valid (GstVp9Parse * self)
|
|
{
|
|
if (self->width <= 0 || self->height <= 0)
|
|
return FALSE;
|
|
|
|
if (self->subsampling_x < 0 || self->subsampling_y < 0)
|
|
return FALSE;
|
|
|
|
if (self->profile == GST_VP9_PROFILE_UNDEFINED)
|
|
return FALSE;
|
|
|
|
if (self->bit_depth < (GstVp9BitDepth) GST_VP9_BIT_DEPTH_8)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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 ||
|
|
frame_hdr->show_existing_frame) {
|
|
/* Need to continue to get some valid info. */
|
|
if (gst_vp9_parse_is_info_valid (self))
|
|
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 == 0 &&
|
|
self->parser->subsampling_y == 0)
|
|
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);
|
|
|
|
gst_caps_set_simple (final_caps, "codec-alpha", G_TYPE_BOOLEAN,
|
|
self->codec_alpha, 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);
|
|
gst_structure_get_boolean (str, "codec-alpha", &self->codec_alpha);
|
|
|
|
/* 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;
|
|
}
|