mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-04 23:46:43 +00:00
2c514ac2f1
This improves the h264parse element to attach LCEVC enhancement data to buffers using the new GstLcevcMeta API. This metadata will eventually be used downstream by LCEVC decoders to enhance the RAW video frame. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7330>
1137 lines
36 KiB
C
1137 lines
36 KiB
C
/* GStreamer
|
|
* Copyright (C) <2007> Jan Schmidt <thaytan@mad.scientist.com>
|
|
* Copyright (C) <2011> Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
|
|
* Copyright (C) <2011> Thibault Saunier <thibault.saunier@collabora.com>
|
|
* Copyright (C) <2011> Collabora ltd
|
|
* Copyright (C) <2011> Nokia Corporation
|
|
* Copyright (C) <2011> Intel Corporation
|
|
*
|
|
* 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 <string.h>
|
|
#include <gst/base/base.h>
|
|
#include <gst/pbutils/pbutils.h>
|
|
#include <gst/codecparsers/gstmpegvideometa.h>
|
|
|
|
#include "gstvideoparserselements.h"
|
|
#include "gstmpegvideoparse.h"
|
|
|
|
GST_DEBUG_CATEGORY (mpegv_parse_debug);
|
|
#define GST_CAT_DEFAULT mpegv_parse_debug
|
|
|
|
static GstStaticPadTemplate src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/mpeg, "
|
|
"mpegversion = (int) [1, 2], "
|
|
"parsed = (boolean) true, " "systemstream = (boolean) false")
|
|
);
|
|
|
|
static GstStaticPadTemplate sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/mpeg, "
|
|
"mpegversion = (int) [1, 2], " "systemstream = (boolean) false")
|
|
);
|
|
|
|
/* Properties */
|
|
#define DEFAULT_PROP_DROP TRUE
|
|
#define DEFAULT_PROP_GOP_SPLIT FALSE
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_DROP,
|
|
PROP_GOP_SPLIT
|
|
};
|
|
|
|
#define parent_class gst_mpegv_parse_parent_class
|
|
G_DEFINE_TYPE (GstMpegvParse, gst_mpegv_parse, GST_TYPE_BASE_PARSE);
|
|
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (mpegvideoparse, "mpegvideoparse",
|
|
GST_RANK_PRIMARY + 1, GST_TYPE_MPEGVIDEO_PARSE,
|
|
videoparsers_element_init (plugin));
|
|
|
|
static gboolean gst_mpegv_parse_start (GstBaseParse * parse);
|
|
static gboolean gst_mpegv_parse_stop (GstBaseParse * parse);
|
|
static GstFlowReturn gst_mpegv_parse_handle_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame, gint * skipsize);
|
|
static GstFlowReturn gst_mpegv_parse_parse_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame);
|
|
static gboolean gst_mpegv_parse_set_caps (GstBaseParse * parse, GstCaps * caps);
|
|
static GstCaps *gst_mpegv_parse_get_caps (GstBaseParse * parse,
|
|
GstCaps * filter);
|
|
static GstFlowReturn gst_mpegv_parse_pre_push_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame);
|
|
static gboolean gst_mpegv_parse_sink_query (GstBaseParse * parse,
|
|
GstQuery * query);
|
|
|
|
static void gst_mpegv_parse_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_mpegv_parse_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static void
|
|
gst_mpegv_parse_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstMpegvParse *parse = GST_MPEGVIDEO_PARSE (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_DROP:
|
|
parse->drop = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_GOP_SPLIT:
|
|
parse->gop_split = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpegv_parse_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstMpegvParse *parse = GST_MPEGVIDEO_PARSE (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_DROP:
|
|
g_value_set_boolean (value, parse->drop);
|
|
break;
|
|
case PROP_GOP_SPLIT:
|
|
g_value_set_boolean (value, parse->gop_split);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpegv_parse_class_init (GstMpegvParseClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (mpegv_parse_debug, "mpegvideoparse", 0,
|
|
"MPEG-1/2 video parser");
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
gobject_class->set_property = gst_mpegv_parse_set_property;
|
|
gobject_class->get_property = gst_mpegv_parse_get_property;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DROP,
|
|
g_param_spec_boolean ("drop", "drop",
|
|
"Drop data until valid configuration data is received either "
|
|
"in the stream or through caps", DEFAULT_PROP_DROP,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_GOP_SPLIT,
|
|
g_param_spec_boolean ("gop-split", "gop-split",
|
|
"Split frame when encountering GOP", DEFAULT_PROP_GOP_SPLIT,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_add_static_pad_template (element_class, &src_template);
|
|
gst_element_class_add_static_pad_template (element_class, &sink_template);
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"MPEG video elementary stream parser",
|
|
"Codec/Parser/Video",
|
|
"Parses and frames MPEG-1 and MPEG-2 elementary video streams",
|
|
"Wim Taymans <wim.taymans@ccollabora.co.uk>, "
|
|
"Jan Schmidt <thaytan@mad.scientist.com>, "
|
|
"Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>");
|
|
|
|
/* Override BaseParse vfuncs */
|
|
parse_class->start = GST_DEBUG_FUNCPTR (gst_mpegv_parse_start);
|
|
parse_class->stop = GST_DEBUG_FUNCPTR (gst_mpegv_parse_stop);
|
|
parse_class->handle_frame = GST_DEBUG_FUNCPTR (gst_mpegv_parse_handle_frame);
|
|
parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_mpegv_parse_set_caps);
|
|
parse_class->get_sink_caps = GST_DEBUG_FUNCPTR (gst_mpegv_parse_get_caps);
|
|
parse_class->pre_push_frame =
|
|
GST_DEBUG_FUNCPTR (gst_mpegv_parse_pre_push_frame);
|
|
parse_class->sink_query = GST_DEBUG_FUNCPTR (gst_mpegv_parse_sink_query);
|
|
}
|
|
|
|
static void
|
|
gst_mpegv_parse_init (GstMpegvParse * parse)
|
|
{
|
|
parse->config_flags = FLAG_NONE;
|
|
|
|
gst_base_parse_set_pts_interpolation (GST_BASE_PARSE (parse), FALSE);
|
|
gst_base_parse_set_infer_ts (GST_BASE_PARSE (parse), FALSE);
|
|
GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (parse));
|
|
GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (parse));
|
|
}
|
|
|
|
static void
|
|
gst_mpegv_parse_reset_frame (GstMpegvParse * mpvparse)
|
|
{
|
|
/* done parsing; reset state */
|
|
mpvparse->last_sc = -1;
|
|
mpvparse->seq_size = 0;
|
|
mpvparse->seq_offset = -1;
|
|
mpvparse->pic_offset = -1;
|
|
mpvparse->frame_repeat_count = 0;
|
|
memset (mpvparse->ext_offsets, 0, sizeof (mpvparse->ext_offsets));
|
|
mpvparse->ext_count = 0;
|
|
mpvparse->slice_count = 0;
|
|
mpvparse->slice_offset = 0;
|
|
gst_video_clear_user_data (&mpvparse->user_data, FALSE);
|
|
}
|
|
|
|
static void
|
|
gst_mpegv_parse_reset (GstMpegvParse * mpvparse)
|
|
{
|
|
gst_mpegv_parse_reset_frame (mpvparse);
|
|
mpvparse->profile = 0;
|
|
mpvparse->update_caps = TRUE;
|
|
mpvparse->send_codec_tag = TRUE;
|
|
mpvparse->send_mpeg_meta = TRUE;
|
|
|
|
gst_buffer_replace (&mpvparse->config, NULL);
|
|
memset (&mpvparse->sequencehdr, 0, sizeof (mpvparse->sequencehdr));
|
|
memset (&mpvparse->sequenceext, 0, sizeof (mpvparse->sequenceext));
|
|
memset (&mpvparse->sequencedispext, 0, sizeof (mpvparse->sequencedispext));
|
|
memset (&mpvparse->pichdr, 0, sizeof (mpvparse->pichdr));
|
|
memset (&mpvparse->picext, 0, sizeof (mpvparse->picext));
|
|
|
|
mpvparse->seqhdr_updated = FALSE;
|
|
mpvparse->seqext_updated = FALSE;
|
|
mpvparse->seqdispext_updated = FALSE;
|
|
mpvparse->picext_updated = FALSE;
|
|
mpvparse->quantmatrext_updated = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpegv_parse_sink_query (GstBaseParse * parse, GstQuery * query)
|
|
{
|
|
gboolean res;
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
|
|
res = GST_BASE_PARSE_CLASS (parent_class)->sink_query (parse, query);
|
|
|
|
if (res && GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION) {
|
|
mpvparse->send_mpeg_meta =
|
|
gst_query_find_allocation_meta (query, GST_MPEG_VIDEO_META_API_TYPE,
|
|
NULL);
|
|
|
|
GST_DEBUG_OBJECT (parse, "Downstream can handle GstMpegVideo GstMeta : %d",
|
|
mpvparse->send_mpeg_meta);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpegv_parse_start (GstBaseParse * parse)
|
|
{
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
|
|
GST_DEBUG_OBJECT (parse, "start");
|
|
|
|
gst_mpegv_parse_reset (mpvparse);
|
|
/* at least this much for a valid frame */
|
|
gst_base_parse_set_min_frame_size (parse, 6);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpegv_parse_stop (GstBaseParse * parse)
|
|
{
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
|
|
GST_DEBUG_OBJECT (parse, "stop");
|
|
|
|
gst_mpegv_parse_reset (mpvparse);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpegv_parse_process_config (GstMpegvParse * mpvparse, GstMapInfo * info,
|
|
guint size)
|
|
{
|
|
GstMpegVideoPacket packet;
|
|
guint8 *data_with_prefix;
|
|
gint i;
|
|
|
|
if (mpvparse->seq_offset < 4) {
|
|
/* This shouldn't happen, but just in case... */
|
|
GST_WARNING_OBJECT (mpvparse, "Sequence header start code missing.");
|
|
return FALSE;
|
|
}
|
|
|
|
packet.data = info->data;
|
|
packet.type = GST_MPEG_VIDEO_PACKET_SEQUENCE;
|
|
packet.offset = mpvparse->seq_offset;
|
|
packet.size = size - mpvparse->seq_offset;
|
|
/* pointer to sequence header data including the start code prefix -
|
|
used for codec private data */
|
|
data_with_prefix = (guint8 *) packet.data + packet.offset - 4;
|
|
|
|
/* only do stuff if something new; only compare first 8 bytes, changes in
|
|
quantiser matrix or bitrate don't matter here. Also changing the
|
|
matrices in codec_data seems to cause problem with decoders */
|
|
if (mpvparse->config &&
|
|
gst_buffer_memcmp (mpvparse->config, 0, data_with_prefix, MIN (size,
|
|
8)) == 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (!gst_mpeg_video_packet_parse_sequence_header (&packet,
|
|
&mpvparse->sequencehdr)) {
|
|
GST_DEBUG_OBJECT (mpvparse,
|
|
"failed to parse config data (size %d) at offset %d",
|
|
size, mpvparse->seq_offset);
|
|
return FALSE;
|
|
}
|
|
|
|
mpvparse->seqhdr_updated = TRUE;
|
|
|
|
GST_LOG_OBJECT (mpvparse, "accepting parsed config size %d", size);
|
|
|
|
/* Set mpeg version, and parse sequence extension */
|
|
mpvparse->config_flags = FLAG_NONE;
|
|
for (i = 0; i < mpvparse->ext_count; ++i) {
|
|
packet.type = GST_MPEG_VIDEO_PACKET_EXTENSION;
|
|
packet.offset = mpvparse->ext_offsets[i];
|
|
packet.size = (gint) size - mpvparse->ext_offsets[i];
|
|
mpvparse->config_flags |= FLAG_MPEG2;
|
|
if (packet.size > 1) {
|
|
switch (packet.data[packet.offset] >> 4) {
|
|
case GST_MPEG_VIDEO_PACKET_EXT_SEQUENCE:
|
|
if (gst_mpeg_video_packet_parse_sequence_extension (&packet,
|
|
&mpvparse->sequenceext)) {
|
|
GST_LOG_OBJECT (mpvparse, "Read Sequence Extension");
|
|
mpvparse->config_flags |= FLAG_SEQUENCE_EXT;
|
|
mpvparse->seqext_updated = TRUE;
|
|
}
|
|
break;
|
|
case GST_MPEG_VIDEO_PACKET_EXT_SEQUENCE_DISPLAY:
|
|
if (gst_mpeg_video_packet_parse_sequence_display_extension (&packet,
|
|
&mpvparse->sequencedispext)) {
|
|
GST_LOG_OBJECT (mpvparse, "Read Sequence Display Extension");
|
|
mpvparse->config_flags |= FLAG_SEQUENCE_DISPLAY_EXT;
|
|
mpvparse->seqdispext_updated = TRUE;
|
|
}
|
|
break;
|
|
case GST_MPEG_VIDEO_PACKET_EXT_QUANT_MATRIX:
|
|
if (gst_mpeg_video_packet_parse_quant_matrix_extension (&packet,
|
|
&mpvparse->quantmatrext)) {
|
|
GST_LOG_OBJECT (mpvparse, "Read Quantization Matrix Extension");
|
|
mpvparse->quantmatrext_updated = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (mpvparse->config_flags & FLAG_MPEG2) {
|
|
/* Update the sequence header based on extensions */
|
|
GstMpegVideoSequenceExt *seqext = NULL;
|
|
GstMpegVideoSequenceDisplayExt *seqdispext = NULL;
|
|
|
|
if (mpvparse->config_flags & FLAG_SEQUENCE_EXT)
|
|
seqext = &mpvparse->sequenceext;
|
|
if (mpvparse->config_flags & FLAG_SEQUENCE_DISPLAY_EXT)
|
|
seqdispext = &mpvparse->sequencedispext;
|
|
|
|
gst_mpeg_video_finalise_mpeg2_sequence_header (&mpvparse->sequencehdr,
|
|
seqext, seqdispext);
|
|
}
|
|
|
|
if (mpvparse->fps_num == 0 || mpvparse->fps_den == 0) {
|
|
mpvparse->fps_num = mpvparse->sequencehdr.fps_n;
|
|
mpvparse->fps_den = mpvparse->sequencehdr.fps_d;
|
|
}
|
|
|
|
/* parsing ok, so accept it as new config */
|
|
if (mpvparse->config != NULL)
|
|
gst_buffer_unref (mpvparse->config);
|
|
|
|
mpvparse->config = gst_buffer_new_and_alloc (size);
|
|
gst_buffer_fill (mpvparse->config, 0, data_with_prefix, size);
|
|
|
|
/* trigger src caps update */
|
|
mpvparse->update_caps = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* FIXME : Move these functions to libgstcodecparser for usage by
|
|
* more elements/code */
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
static const gchar *
|
|
picture_start_code_name (guint8 psc)
|
|
{
|
|
guint i;
|
|
const struct
|
|
{
|
|
guint8 psc;
|
|
const gchar *name;
|
|
} psc_names[] = {
|
|
{
|
|
0x00, "Picture Start"}, {
|
|
0xb0, "Reserved"}, {
|
|
0xb1, "Reserved"}, {
|
|
0xb2, "User Data Start"}, {
|
|
0xb3, "Sequence Header Start"}, {
|
|
0xb4, "Sequence Error"}, {
|
|
0xb5, "Extension Start"}, {
|
|
0xb6, "Reserved"}, {
|
|
0xb7, "Sequence End"}, {
|
|
0xb8, "Group Start"}, {
|
|
0xb9, "Program End"}
|
|
};
|
|
if (psc < 0xB0 && psc > 0)
|
|
return "Slice Start";
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (psc_names); i++)
|
|
if (psc_names[i].psc == psc)
|
|
return psc_names[i].name;
|
|
|
|
return "UNKNOWN";
|
|
};
|
|
|
|
static const gchar *
|
|
picture_type_name (guint8 pct)
|
|
{
|
|
guint i;
|
|
const struct
|
|
{
|
|
guint8 pct;
|
|
const gchar *name;
|
|
} pct_names[] = {
|
|
{
|
|
0, "Forbidden"}, {
|
|
1, "I Frame"}, {
|
|
2, "P Frame"}, {
|
|
3, "B Frame"}, {
|
|
4, "DC Intra Coded (Shall Not Be Used!)"}
|
|
};
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (pct_names); i++)
|
|
if (pct_names[i].pct == pct)
|
|
return pct_names[i].name;
|
|
|
|
return "Reserved/Unknown";
|
|
}
|
|
#endif /* GST_DISABLE_GST_DEBUG */
|
|
|
|
static void
|
|
parse_packet_extension (GstMpegvParse * mpvparse, GstMapInfo * info, guint off)
|
|
{
|
|
GstMpegVideoPacket packet;
|
|
|
|
packet.data = info->data;
|
|
packet.type = GST_MPEG_VIDEO_PACKET_EXTENSION;
|
|
packet.offset = off;
|
|
packet.size = info->size - off;
|
|
|
|
/* FIXME : WE ARE ASSUMING IT IS A *PICTURE* EXTENSION */
|
|
if (gst_mpeg_video_packet_parse_picture_extension (&packet,
|
|
&mpvparse->picext)) {
|
|
mpvparse->frame_repeat_count = 1;
|
|
|
|
if (mpvparse->picext.repeat_first_field) {
|
|
if (mpvparse->sequenceext.progressive) {
|
|
if (mpvparse->picext.top_field_first)
|
|
mpvparse->frame_repeat_count = 5;
|
|
else
|
|
mpvparse->frame_repeat_count = 3;
|
|
} else if (mpvparse->picext.progressive_frame) {
|
|
mpvparse->frame_repeat_count = 2;
|
|
}
|
|
}
|
|
mpvparse->picext_updated = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_user_data_packet (GstMpegvParse * mpvparse, const guint8 * data,
|
|
guint size)
|
|
{
|
|
GstByteReader br;
|
|
GstVideoParseUtilsField field = GST_VIDEO_PARSE_UTILS_FIELD_1;
|
|
gst_byte_reader_init (&br, data, size);
|
|
|
|
if (mpvparse->picext.picture_structure ==
|
|
(guint8) GST_MPEG_VIDEO_PICTURE_STRUCTURE_BOTTOM_FIELD)
|
|
field = GST_VIDEO_PARSE_UTILS_FIELD_2;
|
|
gst_video_parse_user_data ((GstElement *) mpvparse, &mpvparse->user_data, &br,
|
|
field, ITU_T_T35_MANUFACTURER_US_ATSC);
|
|
|
|
}
|
|
|
|
/* caller guarantees at least start code in @buf at @off ( - 4)*/
|
|
/* for off == 4 initial code; returns TRUE if code starts a frame
|
|
* otherwise returns TRUE if code terminates preceding frame */
|
|
static gboolean
|
|
gst_mpegv_parse_process_sc (GstMpegvParse * mpvparse,
|
|
GstMapInfo * info, gint off, GstMpegVideoPacket * packet,
|
|
gboolean * need_more)
|
|
{
|
|
gboolean ret = FALSE, checkconfig = TRUE;
|
|
|
|
GST_LOG_OBJECT (mpvparse, "process startcode %x (%s) offset:%d", packet->type,
|
|
picture_start_code_name (packet->type), off);
|
|
|
|
*need_more = FALSE;
|
|
|
|
switch (packet->type) {
|
|
case GST_MPEG_VIDEO_PACKET_PICTURE:
|
|
GST_LOG_OBJECT (mpvparse, "startcode is PICTURE");
|
|
/* picture is aggregated with preceding sequence/gop, if any.
|
|
* so, picture start code only ends if already a previous one */
|
|
if (mpvparse->pic_offset < 0)
|
|
mpvparse->pic_offset = off;
|
|
else
|
|
ret = (off != mpvparse->pic_offset);
|
|
/* but it's a valid starting one */
|
|
if (off == 4)
|
|
ret = TRUE;
|
|
break;
|
|
case GST_MPEG_VIDEO_PACKET_SEQUENCE:
|
|
GST_LOG_OBJECT (mpvparse, "startcode is SEQUENCE");
|
|
if (mpvparse->seq_offset < 0)
|
|
mpvparse->seq_offset = off;
|
|
ret = TRUE;
|
|
break;
|
|
case GST_MPEG_VIDEO_PACKET_GOP:
|
|
GST_LOG_OBJECT (mpvparse, "startcode is GOP");
|
|
if (mpvparse->seq_offset >= 0)
|
|
ret = mpvparse->gop_split;
|
|
else
|
|
ret = TRUE;
|
|
break;
|
|
case GST_MPEG_VIDEO_PACKET_EXTENSION:
|
|
mpvparse->config_flags |= FLAG_MPEG2;
|
|
GST_LOG_OBJECT (mpvparse, "startcode is VIDEO PACKET EXTENSION");
|
|
if (mpvparse->pic_offset >= 0) {
|
|
GST_LOG_OBJECT (mpvparse, "... considered PICTURE EXTENSION");
|
|
parse_packet_extension (mpvparse, info, off);
|
|
} else {
|
|
GST_LOG_OBJECT (mpvparse, "... considered SEQUENCE EXTENSION");
|
|
if (mpvparse->ext_count < G_N_ELEMENTS (mpvparse->ext_offsets))
|
|
mpvparse->ext_offsets[mpvparse->ext_count++] = off;
|
|
}
|
|
checkconfig = FALSE;
|
|
break;
|
|
case GST_MPEG_VIDEO_PACKET_USER_DATA:
|
|
GST_LOG_OBJECT (mpvparse, "USER_DATA packet of %d bytes", packet->size);
|
|
|
|
if (packet->size < 0) {
|
|
GST_LOG_OBJECT (mpvparse, "no size yet, need more data");
|
|
*need_more = TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
parse_user_data_packet (mpvparse, info->data + off, packet->size);
|
|
checkconfig = FALSE;
|
|
break;
|
|
default:
|
|
if (GST_MPEG_VIDEO_PACKET_IS_SLICE (packet->type)) {
|
|
mpvparse->slice_count++;
|
|
if (mpvparse->slice_offset == 0)
|
|
mpvparse->slice_offset = off - 4;
|
|
}
|
|
checkconfig = FALSE;
|
|
break;
|
|
}
|
|
|
|
/* set size to avoid processing config again */
|
|
if (checkconfig && mpvparse->seq_offset >= 0 && off != mpvparse->seq_offset &&
|
|
!mpvparse->seq_size) {
|
|
/* should always be at start */
|
|
g_assert (mpvparse->seq_offset <= 4);
|
|
gst_mpegv_parse_process_config (mpvparse, info, off - mpvparse->seq_offset);
|
|
mpvparse->seq_size = off - mpvparse->seq_offset;
|
|
}
|
|
|
|
/* extract some picture info if there is any in the frame being terminated */
|
|
if (ret && mpvparse->pic_offset >= 0 && mpvparse->pic_offset < off) {
|
|
GstMpegVideoPacket header;
|
|
|
|
header.data = info->data;
|
|
header.type = GST_MPEG_VIDEO_PACKET_PICTURE;
|
|
header.offset = mpvparse->pic_offset;
|
|
header.size = info->size - mpvparse->pic_offset;
|
|
if (gst_mpeg_video_packet_parse_picture_header (&header, &mpvparse->pichdr))
|
|
GST_LOG_OBJECT (mpvparse, "picture_coding_type %d (%s), ending"
|
|
"frame of size %d", mpvparse->pichdr.pic_type,
|
|
picture_type_name (mpvparse->pichdr.pic_type), off - 4);
|
|
else
|
|
GST_LOG_OBJECT (mpvparse, "Couldn't parse picture at offset %d",
|
|
mpvparse->pic_offset);
|
|
|
|
/* if terminating packet is a picture, we need to check if it has same TSN as the picture that is being
|
|
terminated. If it does, we need to keep those together, as these packets are two fields of the same
|
|
frame */
|
|
if (packet->type == GST_MPEG_VIDEO_PACKET_PICTURE
|
|
&& (mpvparse->config_flags & FLAG_SEQUENCE_EXT)
|
|
&& !mpvparse->sequenceext.progressive) {
|
|
if (info->size - off < 2) { /* we need at least two bytes to read the TSN */
|
|
*need_more = TRUE;
|
|
ret = FALSE;
|
|
} else {
|
|
/* TSN is stored in first 10 bits */
|
|
int tsn = info->data[off] << 2 | (info->data[off + 1] & 0xC0) >> 6;
|
|
|
|
if (tsn == mpvparse->pichdr.tsn) /* prevent termination if TSN is same */
|
|
ret = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* FIXME move into baseparse, or anything equivalent;
|
|
* see https://bugzilla.gnome.org/show_bug.cgi?id=650093
|
|
* #define GST_BASE_PARSE_FRAME_FLAG_PARSING 0x100000 */
|
|
|
|
static inline void
|
|
update_frame_parsing_status (GstMpegvParse * mpvparse,
|
|
GstBaseParseFrame * frame)
|
|
{
|
|
/* avoid stale cached parsing state */
|
|
if (frame->flags & GST_BASE_PARSE_FRAME_FLAG_NEW_FRAME) {
|
|
GST_LOG_OBJECT (mpvparse, "parsing new frame");
|
|
gst_mpegv_parse_reset_frame (mpvparse);
|
|
} else {
|
|
GST_LOG_OBJECT (mpvparse, "resuming frame parsing");
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mpegv_parse_handle_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame, gint * skipsize)
|
|
{
|
|
GstFlowReturn flowret = GST_FLOW_OK;
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
GstBuffer *buf = frame->buffer;
|
|
gboolean ret = FALSE;
|
|
gint off = 0;
|
|
GstMpegVideoPacket packet;
|
|
guint8 *data;
|
|
gint size;
|
|
gboolean need_more = FALSE;
|
|
GstMapInfo map;
|
|
|
|
update_frame_parsing_status (mpvparse, frame);
|
|
|
|
gst_buffer_map (buf, &map, GST_MAP_READ);
|
|
data = map.data;
|
|
size = map.size;
|
|
|
|
retry:
|
|
/* at least start code and subsequent byte */
|
|
if (G_UNLIKELY (size < 5 + off))
|
|
goto exit;
|
|
|
|
/* if already found a previous start code, e.g. start of frame, go for next */
|
|
if (mpvparse->last_sc >= 0) {
|
|
packet.offset = mpvparse->last_sc;
|
|
packet.size = 0;
|
|
goto next;
|
|
}
|
|
|
|
if (!gst_mpeg_video_parse (&packet, data, size, off)) {
|
|
/* didn't find anything that looks like a sync word, skip */
|
|
GST_LOG_OBJECT (mpvparse, "no start code found");
|
|
*skipsize = size - 3;
|
|
goto exit;
|
|
}
|
|
|
|
off = packet.offset - 4;
|
|
GST_LOG_OBJECT (mpvparse, "possible sync at buffer offset %d", off);
|
|
|
|
/* possible frame header, but not at offset 0? skip bytes before sync */
|
|
if (G_UNLIKELY (off > 0)) {
|
|
*skipsize = off;
|
|
goto exit;
|
|
}
|
|
|
|
/* note: initial start code is assumed at offset 0 by subsequent code */
|
|
|
|
/* examine start code, see if it looks like an initial start code */
|
|
if (gst_mpegv_parse_process_sc (mpvparse, &map, 4, &packet, &need_more)) {
|
|
/* found sc */
|
|
GST_LOG_OBJECT (mpvparse, "valid start code found");
|
|
mpvparse->last_sc = 0;
|
|
} else {
|
|
off++;
|
|
gst_mpegv_parse_reset_frame (mpvparse);
|
|
GST_LOG_OBJECT (mpvparse, "invalid start code");
|
|
goto retry;
|
|
}
|
|
|
|
next:
|
|
/* start is fine as of now */
|
|
*skipsize = 0;
|
|
/* terminating start code may have been found in prev scan already */
|
|
if (((gint) packet.size) >= 0) {
|
|
off = packet.offset + packet.size;
|
|
/* so now we have start code at start of data; locate next start code */
|
|
if (!gst_mpeg_video_parse (&packet, data, size, off)) {
|
|
off = -1;
|
|
} else {
|
|
g_assert (packet.offset >= 4);
|
|
off = packet.offset - 4;
|
|
}
|
|
} else {
|
|
off = -1;
|
|
}
|
|
|
|
GST_LOG_OBJECT (mpvparse, "next start code at %d", off);
|
|
if (off < 0) {
|
|
off = size - 3;
|
|
goto need_more;
|
|
} else {
|
|
/* decide whether this startcode ends a frame */
|
|
ret = gst_mpegv_parse_process_sc (mpvparse, &map, off + 4, &packet,
|
|
&need_more);
|
|
/* in rare cases, might need to peek past start code */
|
|
if (need_more) {
|
|
GST_LOG_OBJECT (mpvparse, "need more data (past start code");
|
|
ret = FALSE;
|
|
goto need_more;
|
|
}
|
|
}
|
|
|
|
if (!ret)
|
|
goto next;
|
|
|
|
exit:
|
|
gst_buffer_unmap (buf, &map);
|
|
|
|
if (ret) {
|
|
GstFlowReturn res;
|
|
|
|
*skipsize = 0;
|
|
g_assert (off <= map.size);
|
|
res = gst_mpegv_parse_parse_frame (parse, frame);
|
|
if (res == GST_BASE_PARSE_FLOW_DROPPED)
|
|
frame->flags |= GST_BASE_PARSE_FRAME_FLAG_DROP;
|
|
flowret = gst_base_parse_finish_frame (parse, frame, off);
|
|
/* Reset local information */
|
|
mpvparse->seqhdr_updated = FALSE;
|
|
mpvparse->seqext_updated = FALSE;
|
|
mpvparse->seqdispext_updated = FALSE;
|
|
mpvparse->picext_updated = FALSE;
|
|
mpvparse->quantmatrext_updated = FALSE;
|
|
}
|
|
|
|
return flowret;
|
|
|
|
need_more:
|
|
/* if draining, take all */
|
|
if (GST_BASE_PARSE_DRAINING (parse)) {
|
|
GST_LOG_OBJECT (mpvparse, "draining, accepting all data");
|
|
off = size;
|
|
ret = TRUE;
|
|
} else {
|
|
GST_LOG_OBJECT (mpvparse, "need more data");
|
|
/* resume scan where we left it */
|
|
mpvparse->last_sc = off;
|
|
/* request best next available */
|
|
off = G_MAXUINT;
|
|
}
|
|
goto exit;
|
|
}
|
|
|
|
static void
|
|
gst_mpegv_parse_update_src_caps (GstMpegvParse * mpvparse)
|
|
{
|
|
GstCaps *caps = NULL;
|
|
GstStructure *s = NULL;
|
|
|
|
/* only update if no src caps yet or explicitly triggered */
|
|
if (G_LIKELY (gst_pad_has_current_caps (GST_BASE_PARSE_SRC_PAD (mpvparse)) &&
|
|
!mpvparse->update_caps))
|
|
return;
|
|
|
|
/* carry over input caps as much as possible; override with our own stuff */
|
|
caps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (mpvparse));
|
|
if (caps) {
|
|
caps = gst_caps_make_writable (caps);
|
|
s = gst_caps_get_structure (caps, 0);
|
|
} else {
|
|
caps = gst_caps_new_empty_simple ("video/mpeg");
|
|
}
|
|
|
|
/* typically we don't output buffers until we have properly parsed some
|
|
* config data, so we should at least know about version.
|
|
* If not, it means it has been requested not to drop data, and
|
|
* upstream and/or app must know what they are doing ... */
|
|
gst_caps_set_simple (caps,
|
|
"mpegversion", G_TYPE_INT, (mpvparse->config_flags & FLAG_MPEG2) ? 2 : 1,
|
|
NULL);
|
|
|
|
gst_caps_set_simple (caps, "systemstream", G_TYPE_BOOLEAN, FALSE,
|
|
"parsed", G_TYPE_BOOLEAN, TRUE, NULL);
|
|
|
|
if (mpvparse->sequencehdr.width > 0 && mpvparse->sequencehdr.height > 0) {
|
|
GstMpegVideoSequenceDisplayExt *seqdispext;
|
|
gint width, height;
|
|
|
|
width = mpvparse->sequencehdr.width;
|
|
height = mpvparse->sequencehdr.height;
|
|
|
|
if (mpvparse->config_flags & FLAG_SEQUENCE_DISPLAY_EXT) {
|
|
seqdispext = &mpvparse->sequencedispext;
|
|
|
|
if (seqdispext->display_horizontal_size <= width
|
|
&& seqdispext->display_vertical_size <= height) {
|
|
width = seqdispext->display_horizontal_size;
|
|
height = seqdispext->display_vertical_size;
|
|
GST_INFO_OBJECT (mpvparse,
|
|
"stream has display extension: display_width=%d display_height=%d",
|
|
width, height);
|
|
}
|
|
}
|
|
gst_caps_set_simple (caps, "width", G_TYPE_INT, width,
|
|
"height", G_TYPE_INT, height, NULL);
|
|
}
|
|
|
|
/* perhaps we have a framerate */
|
|
{
|
|
gint fps_num = mpvparse->fps_num;
|
|
gint fps_den = mpvparse->fps_den;
|
|
GstClockTime latency;
|
|
|
|
/* upstream overrides */
|
|
if (s && gst_structure_has_field (s, "framerate"))
|
|
gst_structure_get_fraction (s, "framerate", &fps_num, &fps_den);
|
|
|
|
if (fps_den > 0 && fps_num > 0) {
|
|
gst_caps_set_simple (caps, "framerate",
|
|
GST_TYPE_FRACTION, fps_num, fps_den, NULL);
|
|
gst_base_parse_set_frame_rate (GST_BASE_PARSE (mpvparse),
|
|
fps_num, fps_den, 0, 0);
|
|
latency = gst_util_uint64_scale (GST_SECOND, fps_den, fps_num);
|
|
gst_base_parse_set_latency (GST_BASE_PARSE (mpvparse), latency, latency);
|
|
}
|
|
}
|
|
|
|
/* or pixel-aspect-ratio */
|
|
if (mpvparse->sequencehdr.par_w && mpvparse->sequencehdr.par_h > 0 &&
|
|
(!s || !gst_structure_has_field (s, "pixel-aspect-ratio"))) {
|
|
gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
|
|
mpvparse->sequencehdr.par_w, mpvparse->sequencehdr.par_h, NULL);
|
|
}
|
|
|
|
if (mpvparse->config != NULL) {
|
|
gst_caps_set_simple (caps, "codec_data",
|
|
GST_TYPE_BUFFER, mpvparse->config, NULL);
|
|
}
|
|
|
|
if (mpvparse->config_flags & FLAG_SEQUENCE_EXT) {
|
|
guint8 escape = mpvparse->sequenceext.profile_level_escape_bit;
|
|
const guint profile_c = mpvparse->sequenceext.profile;
|
|
const guint level_c = mpvparse->sequenceext.level;
|
|
const gchar *profile = NULL, *level = NULL;
|
|
/*
|
|
* Profile indication - 1 => High, 2 => Spatially Scalable,
|
|
* 3 => SNR Scalable, 4 => Main, 5 => Simple
|
|
* 4:2:2 and Multi-view have profile = 0, with the escape bit set to 1
|
|
*/
|
|
const gchar *const profiles[] =
|
|
{ "4:2:2", "high", "spatial", "snr", "main", "simple" };
|
|
/*
|
|
* Level indication - 4 => High, 6 => High-1440, 8 => Main, 10 => Low,
|
|
* except in the case of profile = 0
|
|
*/
|
|
const gchar *const levels[] = { "high", "high-1440", "main", "low" };
|
|
|
|
if (escape) {
|
|
/* Non-hierarchical profile */
|
|
switch (level_c) {
|
|
case 2:
|
|
level = levels[0];
|
|
case 5:
|
|
if (!level)
|
|
level = levels[2];
|
|
profile = "4:2:2";
|
|
break;
|
|
case 10:
|
|
level = levels[0];
|
|
case 11:
|
|
if (!level)
|
|
level = levels[1];
|
|
case 13:
|
|
if (!level)
|
|
level = levels[2];
|
|
case 14:
|
|
if (!level)
|
|
level = levels[3];
|
|
profile = "multiview";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
if (profile_c < 6)
|
|
profile = profiles[profile_c];
|
|
|
|
if ((level_c > 3) && (level_c < 11) && (level_c % 2 == 0))
|
|
level = levels[(level_c >> 1) - 2];
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (mpvparse, "profile:'%s' level:'%s'", profile, level);
|
|
|
|
if (profile)
|
|
gst_caps_set_simple (caps, "profile", G_TYPE_STRING, profile, NULL);
|
|
else
|
|
GST_DEBUG_OBJECT (mpvparse, "Invalid profile - %u", profile_c);
|
|
|
|
if (level)
|
|
gst_caps_set_simple (caps, "level", G_TYPE_STRING, level, NULL);
|
|
else
|
|
GST_DEBUG_OBJECT (mpvparse, "Invalid level - %u", level_c);
|
|
|
|
if (!s || !gst_structure_has_field (s, "interlace-mode"))
|
|
gst_caps_set_simple (caps, "interlace-mode",
|
|
G_TYPE_STRING,
|
|
(mpvparse->sequenceext.progressive ? "progressive" : "mixed"), NULL);
|
|
}
|
|
|
|
gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (mpvparse), caps);
|
|
gst_caps_unref (caps);
|
|
|
|
mpvparse->update_caps = FALSE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mpegv_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
|
|
{
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
GstBuffer *buffer = frame->buffer;
|
|
|
|
if (G_UNLIKELY (mpvparse->pichdr.pic_type == GST_MPEG_VIDEO_PICTURE_TYPE_I))
|
|
GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
else
|
|
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
|
|
/* maybe only sequence in this buffer, though not recommended,
|
|
* so mark it as such and force 0 duration */
|
|
if (G_UNLIKELY (mpvparse->pic_offset < 0)) {
|
|
GST_DEBUG_OBJECT (mpvparse, "frame holds no picture data");
|
|
frame->flags |= GST_BASE_PARSE_FRAME_FLAG_NO_FRAME;
|
|
GST_BUFFER_DURATION (buffer) = 0;
|
|
}
|
|
|
|
if (mpvparse->pic_offset > 4) {
|
|
gst_base_parse_set_ts_at_offset (parse, mpvparse->pic_offset - 4);
|
|
}
|
|
|
|
if (mpvparse->frame_repeat_count
|
|
&& GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer))) {
|
|
GST_BUFFER_DURATION (buffer) =
|
|
(1 + mpvparse->frame_repeat_count) * GST_BUFFER_DURATION (buffer) / 2;
|
|
}
|
|
|
|
if (G_UNLIKELY (mpvparse->drop && !mpvparse->config)) {
|
|
GST_DEBUG_OBJECT (mpvparse, "dropping frame as no config yet");
|
|
return GST_BASE_PARSE_FLOW_DROPPED;
|
|
}
|
|
|
|
gst_mpegv_parse_update_src_caps (mpvparse);
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mpegv_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
|
|
{
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
GstTagList *taglist;
|
|
GstMpegVideoMeta *meta;
|
|
GstMpegVideoSequenceHdr *seq_hdr = NULL;
|
|
GstMpegVideoSequenceExt *seq_ext = NULL;
|
|
GstMpegVideoSequenceDisplayExt *disp_ext = NULL;
|
|
GstMpegVideoPictureHdr *pic_hdr = NULL;
|
|
GstMpegVideoPictureExt *pic_ext = NULL;
|
|
GstMpegVideoQuantMatrixExt *quant_ext = NULL;
|
|
GstBuffer *parse_buffer = NULL;
|
|
|
|
/* tag sending done late enough in hook to ensure pending events
|
|
* have already been sent */
|
|
|
|
if (G_UNLIKELY (mpvparse->send_codec_tag)) {
|
|
GstCaps *caps;
|
|
|
|
/* codec tag */
|
|
caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse));
|
|
if (G_UNLIKELY (caps == NULL)) {
|
|
if (GST_PAD_IS_FLUSHING (GST_BASE_PARSE_SRC_PAD (parse))) {
|
|
GST_INFO_OBJECT (parse, "Src pad is flushing");
|
|
return GST_FLOW_FLUSHING;
|
|
} else {
|
|
GST_INFO_OBJECT (parse, "Src pad is not negotiated!");
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
}
|
|
|
|
taglist = gst_tag_list_new_empty ();
|
|
gst_pb_utils_add_codec_description_to_tag_list (taglist,
|
|
GST_TAG_VIDEO_CODEC, caps);
|
|
gst_caps_unref (caps);
|
|
|
|
gst_base_parse_merge_tags (parse, taglist, GST_TAG_MERGE_REPLACE);
|
|
gst_tag_list_unref (taglist);
|
|
|
|
mpvparse->send_codec_tag = FALSE;
|
|
}
|
|
|
|
if (mpvparse->send_mpeg_meta) {
|
|
GstBuffer *buf;
|
|
|
|
if (mpvparse->seqhdr_updated)
|
|
seq_hdr = &mpvparse->sequencehdr;
|
|
if (mpvparse->seqext_updated)
|
|
seq_ext = &mpvparse->sequenceext;
|
|
if (mpvparse->seqdispext_updated)
|
|
disp_ext = &mpvparse->sequencedispext;
|
|
if (mpvparse->picext_updated)
|
|
pic_ext = &mpvparse->picext;
|
|
if (mpvparse->quantmatrext_updated)
|
|
quant_ext = &mpvparse->quantmatrext;
|
|
pic_hdr = &mpvparse->pichdr;
|
|
|
|
GST_DEBUG_OBJECT (mpvparse,
|
|
"Adding GstMpegVideoMeta (slice_count:%d, slice_offset:%d)",
|
|
mpvparse->slice_count, mpvparse->slice_offset);
|
|
|
|
if (frame->out_buffer) {
|
|
buf = frame->out_buffer = gst_buffer_make_writable (frame->out_buffer);
|
|
} else {
|
|
buf = frame->buffer = gst_buffer_make_writable (frame->buffer);
|
|
}
|
|
|
|
meta =
|
|
gst_buffer_add_mpeg_video_meta (buf, seq_hdr, seq_ext, disp_ext,
|
|
pic_hdr, pic_ext, quant_ext);
|
|
meta->num_slices = mpvparse->slice_count;
|
|
meta->slice_offset = mpvparse->slice_offset;
|
|
}
|
|
|
|
if (frame->out_buffer) {
|
|
parse_buffer = frame->out_buffer =
|
|
gst_buffer_make_writable (frame->out_buffer);
|
|
} else {
|
|
parse_buffer = frame->buffer = gst_buffer_make_writable (frame->buffer);
|
|
}
|
|
|
|
if (pic_ext && !pic_ext->progressive_frame) {
|
|
GST_BUFFER_FLAG_SET (parse_buffer, GST_VIDEO_BUFFER_FLAG_INTERLACED);
|
|
if (pic_ext->top_field_first)
|
|
GST_BUFFER_FLAG_SET (parse_buffer, GST_VIDEO_BUFFER_FLAG_TFF);
|
|
}
|
|
gst_video_push_user_data ((GstElement *) mpvparse, &mpvparse->user_data,
|
|
parse_buffer);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpegv_parse_set_caps (GstBaseParse * parse, GstCaps * caps)
|
|
{
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
GstStructure *s;
|
|
const GValue *value;
|
|
GstBuffer *buf;
|
|
|
|
GST_DEBUG_OBJECT (parse, "setcaps called with %" GST_PTR_FORMAT, caps);
|
|
|
|
s = gst_caps_get_structure (caps, 0);
|
|
|
|
if ((value = gst_structure_get_value (s, "codec_data")) != NULL
|
|
&& (buf = gst_value_get_buffer (value))) {
|
|
GstMapInfo map;
|
|
gst_buffer_map (buf, &map, GST_MAP_READ);
|
|
/* best possible parse attempt,
|
|
* src caps are based on sink caps so it will end up in there
|
|
* whether successful or not */
|
|
mpvparse->seq_offset = 4;
|
|
gst_mpegv_parse_process_config (mpvparse, &map, gst_buffer_get_size (buf));
|
|
gst_buffer_unmap (buf, &map);
|
|
gst_mpegv_parse_reset_frame (mpvparse);
|
|
}
|
|
|
|
/* let's not interfere and accept regardless of config parsing success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
remove_fields (GstCaps * caps)
|
|
{
|
|
guint i, n;
|
|
|
|
n = gst_caps_get_size (caps);
|
|
for (i = 0; i < n; i++) {
|
|
GstStructure *s = gst_caps_get_structure (caps, i);
|
|
|
|
gst_structure_remove_field (s, "parsed");
|
|
}
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_mpegv_parse_get_caps (GstBaseParse * parse, GstCaps * filter)
|
|
{
|
|
GstCaps *peercaps, *templ;
|
|
GstCaps *res;
|
|
|
|
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);
|
|
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);
|
|
|
|
if (peercaps) {
|
|
/* Remove the parsed field */
|
|
peercaps = gst_caps_make_writable (peercaps);
|
|
remove_fields (peercaps);
|
|
|
|
res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (peercaps);
|
|
gst_caps_unref (templ);
|
|
} else {
|
|
res = templ;
|
|
}
|
|
|
|
if (filter) {
|
|
GstCaps *intersection;
|
|
|
|
intersection =
|
|
gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (res);
|
|
res = intersection;
|
|
}
|
|
|
|
return res;
|
|
}
|