mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-10 03:19:40 +00:00
168c8406b5
API is now in baseparse in gstreamer. Timestamps in MPEG-TS streams are based on the last timestamp before the start code of the picture. GstBaseParse sets the timestamp based on the beginning of the sequence header, if one exists before the picture. This fixes the case where the timestamp occurs in the MPEG-TS stream between the seq header and picture start code.
901 lines
28 KiB
C
901 lines
28 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/gstbytereader.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,
|
|
PROP_LAST
|
|
};
|
|
|
|
#define parent_class gst_mpegv_parse_parent_class
|
|
G_DEFINE_TYPE (GstMpegvParse, gst_mpegv_parse, GST_TYPE_BASE_PARSE);
|
|
|
|
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 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 untill 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_pad_template (element_class,
|
|
gst_static_pad_template_get (&src_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&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);
|
|
}
|
|
|
|
static void
|
|
gst_mpegv_parse_init (GstMpegvParse * parse)
|
|
{
|
|
parse->config_flags = FLAG_NONE;
|
|
|
|
gst_base_parse_set_pts_interpolation (GST_BASE_PARSE (parse), FALSE);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
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));
|
|
}
|
|
|
|
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, GstBuffer * buf,
|
|
guint size)
|
|
{
|
|
guint8 *data;
|
|
guint8 *data_with_prefix;
|
|
GstMapInfo map;
|
|
gint i, offset;
|
|
|
|
if (mpvparse->seq_offset < 4) {
|
|
/* This shouldn't happen, but just in case... */
|
|
GST_WARNING_OBJECT (mpvparse, "Sequence header start code missing.");
|
|
return FALSE;
|
|
}
|
|
|
|
gst_buffer_map (buf, &map, GST_MAP_READ);
|
|
data = map.data + mpvparse->seq_offset;
|
|
g_assert (size <= map.size);
|
|
/* pointer to sequence header data including the start code prefix -
|
|
used for codec private data */
|
|
data_with_prefix = data - 4;
|
|
|
|
/* only do stuff if something new; only compare first 11 bytes, changes in
|
|
quantiser matrix doesn't matter here. Also changing the matrices in
|
|
codec_data seems to cause problem with decoders */
|
|
if (mpvparse->config && size == gst_buffer_get_size (mpvparse->config) &&
|
|
gst_buffer_memcmp (mpvparse->config, 0, data_with_prefix, MIN (size,
|
|
11)) == 0) {
|
|
gst_buffer_unmap (buf, &map);
|
|
return TRUE;
|
|
}
|
|
|
|
if (!gst_mpeg_video_parse_sequence_header (&mpvparse->sequencehdr, data,
|
|
size - mpvparse->seq_offset, 0)) {
|
|
GST_DEBUG_OBJECT (mpvparse,
|
|
"failed to parse config data (size %d) at offset %d",
|
|
size, mpvparse->seq_offset);
|
|
gst_buffer_unmap (buf, &map);
|
|
return FALSE;
|
|
}
|
|
|
|
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) {
|
|
offset = mpvparse->ext_offsets[i];
|
|
mpvparse->config_flags |= FLAG_MPEG2;
|
|
if (offset < size) {
|
|
if (gst_mpeg_video_parse_sequence_extension (&mpvparse->sequenceext,
|
|
map.data, size, offset)) {
|
|
GST_LOG_OBJECT (mpvparse, "Read Sequence Extension");
|
|
mpvparse->config_flags |= FLAG_SEQUENCE_EXT;
|
|
} else
|
|
if (gst_mpeg_video_parse_sequence_display_extension
|
|
(&mpvparse->sequencedispext, map.data, size, offset)) {
|
|
GST_LOG_OBJECT (mpvparse, "Read Sequence Display Extension");
|
|
mpvparse->config_flags |= FLAG_SEQUENCE_DISPLAY_EXT;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
|
|
gst_buffer_unmap (buf, &map);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#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_picture_extension (GstMpegvParse * mpvparse, GstBuffer * buf, guint off)
|
|
{
|
|
GstMpegVideoPictureExt ext;
|
|
GstMapInfo map;
|
|
|
|
gst_buffer_map (buf, &map, GST_MAP_READ);
|
|
|
|
if (gst_mpeg_video_parse_picture_extension (&ext, map.data, map.size, off)) {
|
|
mpvparse->frame_repeat_count = 1;
|
|
|
|
if (ext.repeat_first_field) {
|
|
if (mpvparse->sequenceext.progressive) {
|
|
if (ext.top_field_first)
|
|
mpvparse->frame_repeat_count = 5;
|
|
else
|
|
mpvparse->frame_repeat_count = 3;
|
|
} else if (ext.progressive_frame) {
|
|
mpvparse->frame_repeat_count = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
gst_buffer_unmap (buf, &map);
|
|
}
|
|
|
|
/* caller guarantees at least start code in @buf at @off */
|
|
/* 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,
|
|
GstBuffer * buf, gint off, guint8 code)
|
|
{
|
|
gboolean ret = FALSE, packet = TRUE;
|
|
|
|
g_return_val_if_fail (buf && gst_buffer_get_size (buf) >= 4, FALSE);
|
|
|
|
GST_LOG_OBJECT (mpvparse, "process startcode %x (%s)", code,
|
|
picture_start_code_name (code));
|
|
|
|
switch (code) {
|
|
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:
|
|
GST_LOG_OBJECT (mpvparse, "startcode is VIDEO PACKET EXTENSION");
|
|
parse_picture_extension (mpvparse, buf, off);
|
|
if (mpvparse->ext_count < G_N_ELEMENTS (mpvparse->ext_offsets))
|
|
mpvparse->ext_offsets[mpvparse->ext_count++] = off;
|
|
/* fall-through */
|
|
default:
|
|
packet = FALSE;
|
|
break;
|
|
}
|
|
|
|
/* set size to avoid processing config again */
|
|
if (mpvparse->seq_offset >= 0 && off != mpvparse->seq_offset &&
|
|
!mpvparse->seq_size && packet) {
|
|
/* should always be at start */
|
|
g_assert (mpvparse->seq_offset <= 4);
|
|
gst_mpegv_parse_process_config (mpvparse, buf, 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) {
|
|
GstMapInfo map;
|
|
|
|
gst_buffer_map (buf, &map, GST_MAP_READ);
|
|
if (gst_mpeg_video_parse_picture_header (&mpvparse->pichdr,
|
|
map.data, map.size, mpvparse->pic_offset))
|
|
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);
|
|
|
|
gst_buffer_unmap (buf, &map);
|
|
}
|
|
|
|
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 0x10000
|
|
|
|
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)
|
|
{
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
GstBuffer *buf = frame->buffer;
|
|
gboolean ret = FALSE;
|
|
gint off = 0;
|
|
GstMpegVideoPacket packet;
|
|
guint8 *data;
|
|
gint size;
|
|
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) {
|
|
off = 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, buf, 4, packet.type)) {
|
|
/* 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) {
|
|
/* 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 = size - 3;
|
|
/* request best next available */
|
|
off = G_MAXUINT;
|
|
goto exit;
|
|
}
|
|
} else {
|
|
/* decide whether this startcode ends a frame */
|
|
ret = gst_mpegv_parse_process_sc (mpvparse, buf, off + 4, packet.type);
|
|
}
|
|
|
|
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;
|
|
return gst_base_parse_finish_frame (parse, frame, off);
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
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) {
|
|
gst_caps_set_simple (caps, "width", G_TYPE_INT, mpvparse->sequencehdr.width,
|
|
"height", G_TYPE_INT, mpvparse->sequencehdr.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) {
|
|
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[] =
|
|
{ "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 (profile_c > 0 && profile_c < 6)
|
|
profile = profiles[profile_c - 1];
|
|
|
|
if ((level_c > 3) && (level_c < 11) && (level_c % 2 == 0))
|
|
level = levels[(level_c >> 1) - 2];
|
|
|
|
if (profile_c == 8) {
|
|
/* Non-hierarchical profile */
|
|
switch (level_c) {
|
|
case 2:
|
|
level = levels[0];
|
|
case 5:
|
|
level = levels[2];
|
|
profile = "4:2:2";
|
|
break;
|
|
case 10:
|
|
level = levels[0];
|
|
case 11:
|
|
level = levels[1];
|
|
case 13:
|
|
level = levels[2];
|
|
case 14:
|
|
level = levels[3];
|
|
profile = "multiview";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* FIXME does it make sense to expose profile/level in the caps ? */
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
gst_mpegv_parse_update_src_caps (mpvparse);
|
|
|
|
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;
|
|
} else
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mpegv_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
|
|
{
|
|
GstMpegvParse *mpvparse = GST_MPEGVIDEO_PARSE (parse);
|
|
GstTagList *taglist;
|
|
|
|
/* tag sending done late enough in hook to ensure pending events
|
|
* have already been sent */
|
|
|
|
if (G_UNLIKELY (mpvparse->send_codec_tag)) {
|
|
gchar *codec;
|
|
|
|
/* codec tag */
|
|
codec =
|
|
g_strdup_printf ("MPEG %d Video",
|
|
(mpvparse->config_flags & FLAG_MPEG2) ? 2 : 1);
|
|
taglist = gst_tag_list_new (GST_TAG_VIDEO_CODEC, codec, NULL);
|
|
g_free (codec);
|
|
|
|
gst_pad_push_event (GST_BASE_PARSE_SRC_PAD (mpvparse),
|
|
gst_event_new_tag (taglist));
|
|
|
|
mpvparse->send_codec_tag = FALSE;
|
|
}
|
|
|
|
/* usual clipping applies */
|
|
frame->flags |= GST_BASE_PARSE_FRAME_FLAG_CLIP;
|
|
|
|
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))) {
|
|
/* best possible parse attempt,
|
|
* src caps are based on sink caps so it will end up in there
|
|
* whether sucessful or not */
|
|
gst_mpegv_parse_process_config (mpvparse, buf, gst_buffer_get_size (buf));
|
|
gst_mpegv_parse_reset_frame (mpvparse);
|
|
}
|
|
|
|
/* let's not interfere and accept regardless of config parsing success */
|
|
return TRUE;
|
|
}
|
|
|
|
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));
|
|
peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), filter);
|
|
if (peercaps) {
|
|
guint i, n;
|
|
|
|
/* Remove the parsed field */
|
|
peercaps = gst_caps_make_writable (peercaps);
|
|
n = gst_caps_get_size (peercaps);
|
|
for (i = 0; i < n; i++) {
|
|
GstStructure *s = gst_caps_get_structure (peercaps, i);
|
|
gst_structure_remove_field (s, "parsed");
|
|
}
|
|
|
|
res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (peercaps);
|
|
res = gst_caps_make_writable (res);
|
|
|
|
/* Append the template caps because we still want to accept
|
|
* caps without any fields in the case upstream does not
|
|
* know anything.
|
|
*/
|
|
gst_caps_append (res, 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;
|
|
}
|