From afb5b28d66b7c1f0384aa2c1003571a2b51638c1 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Thu, 12 May 2011 12:07:39 +0200 Subject: [PATCH] mpeg4videoparse: port to baseparse --- gst/mpeg4videoparse/Makefile.am | 6 +- gst/mpeg4videoparse/mpeg4parse.c | 290 ++++++++ gst/mpeg4videoparse/mpeg4parse.h | 63 ++ gst/mpeg4videoparse/mpeg4videoparse.c | 1292 ++++++++++----------------------- gst/mpeg4videoparse/mpeg4videoparse.h | 43 +- 5 files changed, 772 insertions(+), 922 deletions(-) create mode 100644 gst/mpeg4videoparse/mpeg4parse.c create mode 100644 gst/mpeg4videoparse/mpeg4parse.h diff --git a/gst/mpeg4videoparse/Makefile.am b/gst/mpeg4videoparse/Makefile.am index e0c4303..8259ed5 100644 --- a/gst/mpeg4videoparse/Makefile.am +++ b/gst/mpeg4videoparse/Makefile.am @@ -1,13 +1,13 @@ plugin_LTLIBRARIES = libgstmpeg4videoparse.la -libgstmpeg4videoparse_la_SOURCES = mpeg4videoparse.c -libgstmpeg4videoparse_la_CFLAGS = $(GST_CFLAGS) +libgstmpeg4videoparse_la_SOURCES = mpeg4videoparse.c mpeg4parse.c +libgstmpeg4videoparse_la_CFLAGS = $(GST_BASE_CFLAGS) $(GST_CFLAGS) libgstmpeg4videoparse_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) libgstmpeg4videoparse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstmpeg4videoparse_la_LIBTOOLFLAGS = --tag=disable-static -noinst_HEADERS = mpeg4videoparse.h +noinst_HEADERS = mpeg4videoparse.h mpeg4parse.h Android.mk: Makefile.am $(BUILT_SOURCES) androgenizer \ diff --git a/gst/mpeg4videoparse/mpeg4parse.c b/gst/mpeg4videoparse/mpeg4parse.c new file mode 100644 index 0000000..667111d --- /dev/null +++ b/gst/mpeg4videoparse/mpeg4parse.c @@ -0,0 +1,290 @@ +/* GStreamer MPEG4-2 video Parser + * Copyright (C) <2008> Mindfruit B.V. + * @author Sjoerd Simons + * Copyright (C) <2007> Julien Moutte + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "mpeg4parse.h" + +#include + +GST_DEBUG_CATEGORY_EXTERN (mpeg4v_parse_debug); +#define GST_CAT_DEFAULT mpeg4v_parse_debug + + +#define GET_BITS(b, num, bits) G_STMT_START { \ + if (!gst_bit_reader_get_bits_uint32(b, bits, num)) \ + goto failed; \ +} G_STMT_END + +#define MARKER_BIT(b) G_STMT_START { \ + guint32 i; \ + GET_BITS(b, 1, &i); \ + if (i != 0x1) \ + goto failed; \ +} G_STMT_END + +static inline gboolean +next_start_code (GstBitReader * b) +{ + guint32 bits; + + GET_BITS (b, 1, &bits); + if (bits != 0) + goto failed; + + while (b->bit != 0) { + GET_BITS (b, 1, &bits); + if (bits != 0x1) + goto failed; + } + + return TRUE; + +failed: + return FALSE; +} + +static inline gboolean +skip_user_data (GstBitReader * bs, guint32 * bits) +{ + while (*bits == MPEG4_USER_DATA_STARTCODE_MARKER) { + guint32 b; + + do { + GET_BITS (bs, 8, &b); + *bits = (*bits << 8) | b; + } while ((*bits >> 8) != MPEG4_START_MARKER); + } + + return TRUE; + +failed: + return FALSE; +} + + +static gint aspect_ratio_table[6][2] = { + {-1, -1}, {1, 1}, {12, 11}, {10, 11}, {16, 11}, {40, 33} +}; + +static gboolean +gst_mpeg4_params_parse_vo (MPEG4Params * params, GstBitReader * br) +{ + guint32 bits; + guint16 time_increment_resolution = 0; + guint16 fixed_time_increment = 0; + gint aspect_ratio_width = -1, aspect_ratio_height = -1; + gint height = -1, width = -1; + + /* expecting a video object startcode */ + GET_BITS (br, 32, &bits); + if (bits > 0x11F) + goto failed; + + /* expecting a video object layer startcode */ + GET_BITS (br, 32, &bits); + if (bits < 0x120 || bits > 0x12F) + goto failed; + + /* ignore random accessible vol and video object type indication */ + GET_BITS (br, 9, &bits); + + GET_BITS (br, 1, &bits); + if (bits) { + /* skip video object layer verid and priority */ + GET_BITS (br, 7, &bits); + } + + /* aspect ratio info */ + GET_BITS (br, 4, &bits); + if (bits == 0) + goto failed; + + /* check if aspect ratio info is extended par */ + if (bits == 0xf) { + GET_BITS (br, 8, &bits); + aspect_ratio_width = bits; + GET_BITS (br, 8, &bits); + aspect_ratio_height = bits; + } else if (bits < 0x6) { + aspect_ratio_width = aspect_ratio_table[bits][0]; + aspect_ratio_height = aspect_ratio_table[bits][1]; + } + + GET_BITS (br, 1, &bits); + if (bits) { + /* vol control parameters, skip chroma and low delay */ + GET_BITS (br, 3, &bits); + GET_BITS (br, 1, &bits); + if (bits) { + /* skip vbv_parameters */ + GET_BITS (br, 79, &bits); + } + } + + /* layer shape */ + GET_BITS (br, 2, &bits); + /* only support rectangular */ + if (bits != 0) + goto failed; + + MARKER_BIT (br); + GET_BITS (br, 16, &bits); + time_increment_resolution = bits; + MARKER_BIT (br); + + GST_DEBUG ("time increment resolution %d", time_increment_resolution); + + GET_BITS (br, 1, &bits); + if (bits) { + /* fixed time increment */ + int n; + + /* Length of the time increment is the minimal number of bits needed to + * represent time_increment_resolution */ + for (n = 0; (time_increment_resolution >> n) != 0; n++); + GET_BITS (br, n, &bits); + + fixed_time_increment = bits; + } else { + /* When fixed_vop_rate is not set we can't guess any framerate */ + fixed_time_increment = 0; + } + GST_DEBUG ("fixed time increment %d", fixed_time_increment); + + /* assuming rectangular shape */ + MARKER_BIT (br); + GET_BITS (br, 13, &bits); + width = bits; + MARKER_BIT (br); + GET_BITS (br, 13, &bits); + height = bits; + MARKER_BIT (br); + + /* so we got it all, report back */ + params->width = width; + params->height = height; + params->time_increment_resolution = time_increment_resolution; + params->fixed_time_increment = fixed_time_increment; + params->aspect_ratio_width = aspect_ratio_width; + params->aspect_ratio_height = aspect_ratio_height; + + return TRUE; + + /* ERRORS */ +failed: + { + GST_WARNING ("Failed to parse config data"); + return FALSE; + } +} + +static gboolean +gst_mpeg4_params_parse_vos (MPEG4Params * params, GstBitReader * br) +{ + guint32 bits; + + GET_BITS (br, 32, &bits); + if (bits != MPEG4_VOS_STARTCODE_MARKER) + goto failed; + + GET_BITS (br, 8, &bits); + params->profile = bits; + + /* invalid profile, warn but carry on */ + if (params->profile == 0) { + GST_WARNING ("Invalid profile in VOS"); + } + + /* Expect Visual Object startcode */ + GET_BITS (br, 32, &bits); + + /* but skip optional user data */ + if (!skip_user_data (br, &bits)) + goto failed; + + if (bits != MPEG4_VISUAL_OBJECT_STARTCODE_MARKER) + goto failed; + + GET_BITS (br, 1, &bits); + if (bits == 0x1) { + /* Skip visual_object_verid and priority */ + GET_BITS (br, 7, &bits); + } + + GET_BITS (br, 4, &bits); + /* Only support video ID */ + if (bits != 0x1) + goto failed; + + /* video signal type */ + GET_BITS (br, 1, &bits); + + if (bits == 0x1) { + /* video signal type, ignore format and range */ + GET_BITS (br, 4, &bits); + + GET_BITS (br, 1, &bits); + if (bits == 0x1) { + /* ignore color description */ + GET_BITS (br, 24, &bits); + } + } + + if (!next_start_code (br)) + goto failed; + + /* skip optional user data */ + GET_BITS (br, 32, &bits); + if (!skip_user_data (br, &bits)) + goto failed; + + /* rewind to start code */ + gst_bit_reader_set_pos (br, gst_bit_reader_get_pos (br) - 32); + + return gst_mpeg4_params_parse_vo (params, br); + + /* ERRORS */ +failed: + { + GST_WARNING ("Failed to parse config data"); + return FALSE; + } +} + +gboolean +gst_mpeg4_params_parse_config (MPEG4Params * params, const guint8 * data, + guint size) +{ + GstBitReader br; + + if (size < 4) + return FALSE; + + gst_bit_reader_init (&br, data, size); + + if (data[3] == MPEG4_VOS_STARTCODE) + return gst_mpeg4_params_parse_vos (params, &br); + else + return gst_mpeg4_params_parse_vo (params, &br); +} diff --git a/gst/mpeg4videoparse/mpeg4parse.h b/gst/mpeg4videoparse/mpeg4parse.h new file mode 100644 index 0000000..cf79e88 --- /dev/null +++ b/gst/mpeg4videoparse/mpeg4parse.h @@ -0,0 +1,63 @@ +/* GStreamer MPEG4-2 video Parser + * Copyright (C) <2008> Mindfruit B.V. + * @author Sjoerd Simons + * Copyright (C) <2007> Julien Moutte + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_MPEG4_PARAMS_H__ +#define __GST_MPEG4_PARAMS_H__ + +#include + +G_BEGIN_DECLS + +#define MPEG4_VIDEO_OBJECT_STARTCODE_MIN 0x00 +#define MPEG4_VIDEO_OBJECT_STARTCODE_MAX 0x1F +#define MPEG4_VOS_STARTCODE 0xB0 +#define MPEG4_VOS_ENDCODE 0xB1 +#define MPEG4_USER_DATA_STARTCODE 0xB2 +#define MPEG4_GOP_STARTCODE 0xB3 +#define MPEG4_VISUAL_OBJECT_STARTCODE 0xB5 +#define MPEG4_VOP_STARTCODE 0xB6 + +#define MPEG4_START_MARKER 0x000001 +#define MPEG4_VISUAL_OBJECT_STARTCODE_MARKER \ + ((MPEG4_START_MARKER << 8) + MPEG4_VISUAL_OBJECT_STARTCODE) +#define MPEG4_VOS_STARTCODE_MARKER \ + ((MPEG4_START_MARKER << 8) + MPEG4_VOS_STARTCODE) +#define MPEG4_USER_DATA_STARTCODE_MARKER \ + ((MPEG4_START_MARKER << 8) + MPEG4_USER_DATA_STARTCODE) + + +typedef struct _MPEG4Params MPEG4Params; + +struct _MPEG4Params +{ + gint profile; + + gint width, height; + gint aspect_ratio_width, aspect_ratio_height; + gint time_increment_resolution; + gint fixed_time_increment; +}; + +GstFlowReturn gst_mpeg4_params_parse_config (MPEG4Params * params, + const guint8 * data, guint size); + +G_END_DECLS +#endif diff --git a/gst/mpeg4videoparse/mpeg4videoparse.c b/gst/mpeg4videoparse/mpeg4videoparse.c index 254db9f..ebb818a 100644 --- a/gst/mpeg4videoparse/mpeg4videoparse.c +++ b/gst/mpeg4videoparse/mpeg4videoparse.c @@ -2,6 +2,9 @@ * Copyright (C) <2008> Mindfruit B.V. * @author Sjoerd Simons * Copyright (C) <2007> Julien Moutte + * Copyright (C) <2011> Mark Nauwelaerts + * Copyright (C) <2011> Collabora Multimedia + * Copyright (C) <2011> Nokia Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -24,9 +27,10 @@ #endif #include +#include #include "mpeg4videoparse.h" -GST_DEBUG_CATEGORY_STATIC (mpeg4v_parse_debug); +GST_DEBUG_CATEGORY (mpeg4v_parse_debug); #define GST_CAT_DEFAULT mpeg4v_parse_debug static GstStaticPadTemplate src_template = @@ -57,1024 +61,532 @@ enum PROP_LAST }; -GST_BOILERPLATE (GstMpeg4VParse, gst_mpeg4vparse, GstElement, GST_TYPE_ELEMENT); +GST_BOILERPLATE (GstMpeg4VParse, gst_mpeg4vparse, GstBaseParse, + GST_TYPE_BASE_PARSE); -static gboolean -gst_mpeg4vparse_set_new_caps (GstMpeg4VParse * parse, - guint16 time_increment_resolution, guint16 fixed_time_increment, - gint aspect_ratio_width, gint aspect_ratio_height, gint width, gint height) -{ - gboolean res; - GstCaps *out_caps; - - if (parse->sink_caps) { - out_caps = gst_caps_copy (parse->sink_caps); - } else { - out_caps = gst_caps_new_simple ("video/mpeg", - "mpegversion", G_TYPE_INT, 4, NULL); - } - gst_caps_set_simple (out_caps, "systemstream", G_TYPE_BOOLEAN, FALSE, - "parsed", G_TYPE_BOOLEAN, TRUE, NULL); +static gboolean gst_mpeg4vparse_start (GstBaseParse * parse); +static gboolean gst_mpeg4vparse_stop (GstBaseParse * parse); +static gboolean gst_mpeg4vparse_check_valid_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, guint * framesize, gint * skipsize); +static GstFlowReturn gst_mpeg4vparse_parse_frame (GstBaseParse * parse, + GstBaseParseFrame * frame); +static GstFlowReturn gst_mpeg4vparse_pre_push_frame (GstBaseParse * parse, + GstBaseParseFrame * frame); +static gboolean gst_mpeg4vparse_set_caps (GstBaseParse * parse, GstCaps * caps); - if (parse->profile != 0) { - gchar *profile = NULL; +static void gst_mpeg4vparse_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_mpeg4vparse_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); - /* FIXME does it make sense to expose the profile in the caps ? */ - profile = g_strdup_printf ("%d", parse->profile); - gst_caps_set_simple (out_caps, "profile-level-id", - G_TYPE_STRING, profile, NULL); - g_free (profile); - } - - if (parse->config != NULL) { - gst_caps_set_simple (out_caps, "codec_data", - GST_TYPE_BUFFER, parse->config, NULL); - } - - if (fixed_time_increment != 0) { - /* we have a framerate */ - gst_caps_set_simple (out_caps, "framerate", - GST_TYPE_FRACTION, time_increment_resolution, fixed_time_increment, - NULL); - parse->frame_duration = gst_util_uint64_scale_int (GST_SECOND, - fixed_time_increment, time_increment_resolution); - } else { - /* unknown duration */ - parse->frame_duration = 0; - } - - if (aspect_ratio_width > 0 && aspect_ratio_height > 0) { - gst_caps_set_simple (out_caps, "pixel-aspect-ratio", - GST_TYPE_FRACTION, aspect_ratio_width, aspect_ratio_height, NULL); - } - - if (width > 0 && height > 0) { - gst_caps_set_simple (out_caps, - "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, NULL); - } - - GST_DEBUG_OBJECT (parse, "setting downstream caps to %" GST_PTR_FORMAT, - out_caps); - res = gst_pad_set_caps (parse->srcpad, out_caps); - gst_caps_unref (out_caps); +static void +gst_mpeg4vparse_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); - parse->have_src_caps = TRUE; - if (parse->pending_segment != NULL) { - /* We can send pending events since we now have caps for the srcpad */ - gst_pad_push_event (parse->srcpad, parse->pending_segment); - parse->pending_segment = NULL; + 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)); - if (G_UNLIKELY (parse->pending_events != NULL)) { - GList *l; + gst_element_class_set_details_simple (element_class, + "MPEG 4 video elementary stream parser", "Codec/Parser/Video", + "Parses MPEG-4 Part 2 elementary video streams", + "Julien Moutte "); +} - for (l = parse->pending_events; l != NULL; l = l->next) - gst_pad_push_event (parse->srcpad, GST_EVENT (l->data)); +static void +gst_mpeg4vparse_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (object); - g_list_free (parse->pending_events); - parse->pending_events = NULL; - } + switch (property_id) { + case PROP_DROP: + parse->drop = g_value_get_boolean (value); + break; + case PROP_CONFIG_INTERVAL: + parse->interval = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } - return res; } -#define VIDEO_OBJECT_STARTCODE_MIN 0x00 -#define VIDEO_OBJECT_STARTCODE_MAX 0x1F -#define VOS_STARTCODE 0xB0 -#define VOS_ENDCODE 0xB1 -#define USER_DATA_STARTCODE 0xB2 -#define GOP_STARTCODE 0xB3 -#define VISUAL_OBJECT_STARTCODE 0xB5 -#define VOP_STARTCODE 0xB6 - -#define START_MARKER 0x000001 -#define VISUAL_OBJECT_STARTCODE_MARKER ((START_MARKER << 8) + VISUAL_OBJECT_STARTCODE) -#define USER_DATA_STARTCODE_MARKER ((START_MARKER << 8) + USER_DATA_STARTCODE) - -typedef struct +static void +gst_mpeg4vparse_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) { - const guint8 *data; - /* byte offset */ - gsize offset; - /* bit offset */ - gsize b_offset; + GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (object); - /* size in bytes */ - gsize size; -} bitstream_t; + switch (property_id) { + case PROP_DROP: + g_value_set_boolean (value, parse->drop); + break; + case PROP_CONFIG_INTERVAL: + g_value_set_uint (value, parse->interval); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} -static gboolean -get_bits (bitstream_t * b, int num, guint32 * bits) +static void +gst_mpeg4vparse_class_init (GstMpeg4VParseClass * klass) { - *bits = 0; - - if (b->offset + ((b->b_offset + num) / 8) > b->size) - return FALSE; + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass); - if (b->b_offset + num <= 8) { - *bits = b->data[b->offset]; - *bits = (*bits >> (8 - num - b->b_offset)) & (((1 << num)) - 1); + parent_class = g_type_class_peek_parent (klass); - b->offset += (b->b_offset + num) / 8; - b->b_offset = (b->b_offset + num) % 8; - return TRUE; - } else { - /* going over the edge.. */ - int next; + gobject_class->set_property = gst_mpeg4vparse_set_property; + gobject_class->get_property = gst_mpeg4vparse_get_property; - next = (8 - b->b_offset); - do { - guint32 t; + 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)); - if (!get_bits (b, next, &t)) - return FALSE; - *bits <<= next; - *bits |= t; - num -= next; - next = MIN (8, num); - } while (num > 0); + g_object_class_install_property (gobject_class, PROP_CONFIG_INTERVAL, + g_param_spec_uint ("config-interval", + "Configuration Send Interval", + "Send Configuration Insertion Interval in seconds (configuration headers " + "will be multiplexed in the data stream when detected.) (0 = disabled)", + 0, 3600, DEFAULT_CONFIG_INTERVAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - return TRUE; - } + /* Override BaseParse vfuncs */ + parse_class->start = GST_DEBUG_FUNCPTR (gst_mpeg4vparse_start); + parse_class->stop = GST_DEBUG_FUNCPTR (gst_mpeg4vparse_stop); + parse_class->check_valid_frame = + GST_DEBUG_FUNCPTR (gst_mpeg4vparse_check_valid_frame); + parse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_mpeg4vparse_parse_frame); + parse_class->pre_push_frame = + GST_DEBUG_FUNCPTR (gst_mpeg4vparse_pre_push_frame); + parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_mpeg4vparse_set_caps); } -#define GET_BITS(b, num, bits) G_STMT_START { \ - if (!get_bits(b, num, bits)) \ - goto failed; \ -} G_STMT_END - -#define MARKER_BIT(b) G_STMT_START { \ - guint32 i; \ - GET_BITS(b, 1, &i); \ - if (i != 0x1) \ - goto failed; \ -} G_STMT_END - -static inline gboolean -next_start_code (bitstream_t * b) +static void +gst_mpeg4vparse_init (GstMpeg4VParse * parse, GstMpeg4VParseClass * g_class) { - guint32 bits; - - GET_BITS (b, 1, &bits); - if (bits != 0) - goto failed; - - while (b->b_offset != 0) { - GET_BITS (b, 1, &bits); - if (bits != 0x1) - goto failed; - } - - return TRUE; - -failed: - return FALSE; + parse->interval = DEFAULT_CONFIG_INTERVAL; + parse->last_report = GST_CLOCK_TIME_NONE; } -static gint aspect_ratio_table[6][2] = { {-1, -1}, {1, 1}, {12, 11}, -{10, 11}, {16, 11}, {40, 33} -}; - static void -gst_mpeg4vparse_set_config (GstMpeg4VParse * parse, const guint8 * data, - gsize size) +gst_mpeg4vparse_reset_frame (GstMpeg4VParse * mp4vparse) { - /* limit possible caps noise */ - if (parse->config && size == GST_BUFFER_SIZE (parse->config) && - memcmp (GST_BUFFER_DATA (parse->config), data, size) == 0) - return; + /* done parsing; reset state */ + mp4vparse->last_sc = -1; + mp4vparse->vop_offset = -1; + mp4vparse->vos_offset = -1; + mp4vparse->vo_offset = -1; +} - if (parse->config != NULL) - gst_buffer_unref (parse->config); +static void +gst_mpeg4vparse_reset (GstMpeg4VParse * mp4vparse) +{ + gst_mpeg4vparse_reset_frame (mp4vparse); + mp4vparse->profile = 0; + mp4vparse->update_caps = TRUE; - parse->config = gst_buffer_new_and_alloc (size); - memcpy (GST_BUFFER_DATA (parse->config), data, size); + gst_buffer_replace (&mp4vparse->config, NULL); + memset (&mp4vparse->params, 0, sizeof (mp4vparse->params)); } -/* Handle parsing a video object */ static gboolean -gst_mpeg4vparse_handle_vo (GstMpeg4VParse * parse, const guint8 * data, - gsize size, gboolean set_codec_data) +gst_mpeg4vparse_start (GstBaseParse * parse) { - guint32 bits; - bitstream_t bs = { data, 0, 0, size }; - guint16 time_increment_resolution = 0; - guint16 fixed_time_increment = 0; - gint aspect_ratio_width = -1, aspect_ratio_height = -1; - gint height = -1, width = -1; - - if (set_codec_data) - gst_mpeg4vparse_set_config (parse, data, size); - - /* expecting a video object startcode */ - GET_BITS (&bs, 32, &bits); - if (bits > 0x11F) - goto failed; - - /* expecting a video object layer startcode */ - GET_BITS (&bs, 32, &bits); - if (bits < 0x120 || bits > 0x12F) - goto failed; - - /* ignore random accessible vol and video object type indication */ - GET_BITS (&bs, 9, &bits); - - GET_BITS (&bs, 1, &bits); - if (bits) { - /* skip video object layer verid and priority */ - GET_BITS (&bs, 7, &bits); - } + GstMpeg4VParse *mp4vparse = GST_MPEG4VIDEOPARSE (parse); - /* aspect ratio info */ - GET_BITS (&bs, 4, &bits); - if (bits == 0) - goto failed; - - /* check if aspect ratio info is extended par */ - if (bits == 0xf) { - GET_BITS (&bs, 8, &bits); - aspect_ratio_width = bits; - GET_BITS (&bs, 8, &bits); - aspect_ratio_height = bits; - } else if (bits < 0x6) { - aspect_ratio_width = aspect_ratio_table[bits][0]; - aspect_ratio_height = aspect_ratio_table[bits][1]; - } + GST_DEBUG_OBJECT (parse, "start"); - GET_BITS (&bs, 1, &bits); - if (bits) { - /* vol control parameters, skip chroma and low delay */ - GET_BITS (&bs, 3, &bits); - GET_BITS (&bs, 1, &bits); - if (bits) { - /* skip vbv_parameters */ - GET_BITS (&bs, 79, &bits); - } - } - - /* layer shape */ - GET_BITS (&bs, 2, &bits); - /* only support rectangular */ - if (bits != 0) - goto failed; - - MARKER_BIT (&bs); - GET_BITS (&bs, 16, &bits); - time_increment_resolution = bits; - MARKER_BIT (&bs); - - GST_DEBUG_OBJECT (parse, "time increment resolution %d", - time_increment_resolution); - - GET_BITS (&bs, 1, &bits); - if (bits) { - /* fixed time increment */ - int n; + gst_mpeg4vparse_reset (mp4vparse); + gst_base_parse_set_min_frame_size (parse, 512); - /* Length of the time increment is the minimal number of bits needed to - * represent time_increment_resolution */ - for (n = 0; (time_increment_resolution >> n) != 0; n++); - GET_BITS (&bs, n, &bits); - - fixed_time_increment = bits; - } else { - /* When fixed_vop_rate is not set we can't guess any framerate */ - fixed_time_increment = 0; - } - GST_DEBUG_OBJECT (parse, "fixed time increment %d", fixed_time_increment); - - /* assuming rectangular shape */ - MARKER_BIT (&bs); - GET_BITS (&bs, 13, &bits); - width = bits; - MARKER_BIT (&bs); - GET_BITS (&bs, 13, &bits); - height = bits; - MARKER_BIT (&bs); - - /* ok we know there is enough data in the stream to decode it and we can start - * pushing the data */ - parse->have_config = TRUE; - -out: - return gst_mpeg4vparse_set_new_caps (parse, time_increment_resolution, - fixed_time_increment, aspect_ratio_width, aspect_ratio_height, - width, height); - - /* ERRORS */ -failed: - { - GST_WARNING_OBJECT (parse, "Failed to parse config data"); - goto out; - } + return TRUE; } -static inline gboolean -skip_user_data (bitstream_t * bs, guint32 * bits) +static gboolean +gst_mpeg4vparse_stop (GstBaseParse * parse) { - while (*bits == USER_DATA_STARTCODE_MARKER) { - guint32 b; + GstMpeg4VParse *mp4vparse = GST_MPEG4VIDEOPARSE (parse); - do { - GET_BITS (bs, 8, &b); - *bits = (*bits << 8) | b; - } while ((*bits >> 8) != START_MARKER); - } + GST_DEBUG_OBJECT (parse, "stop"); - return TRUE; + gst_mpeg4vparse_reset (mp4vparse); -failed: - return FALSE; + return TRUE; } -/* Handle parsing a visual object sequence. - Returns whether we successfully set the caps downstream if needed */ static gboolean -gst_mpeg4vparse_handle_vos (GstMpeg4VParse * parse, const guint8 * data, +gst_mpeg4vparse_process_config (GstMpeg4VParse * mp4vparse, const guint8 * data, gsize size) { - /* Skip the startcode */ - guint32 bits; - - guint8 profile; - gboolean equal; - bitstream_t bs = { data, 0, 0, size }; - - if (size < 5) - goto failed; - - /* Parse the config from the VOS frame */ - bs.offset = 5; - - profile = data[4]; - - /* invalid profile, yikes */ - if (profile == 0) { - GST_WARNING_OBJECT (parse, "Invalid profile in VOS"); - return FALSE; - } - - equal = FALSE; - if (G_LIKELY (parse->config && size == GST_BUFFER_SIZE (parse->config) && - memcmp (GST_BUFFER_DATA (parse->config), data, size) == 0)) - equal = TRUE; - - if (G_LIKELY (parse->profile == profile && equal)) { - /* We know this profile and config data, so we can just keep the same caps - */ + /* only do stuff if something new */ + if (mp4vparse->config && size == GST_BUFFER_SIZE (mp4vparse->config) && + memcmp (GST_BUFFER_DATA (mp4vparse->config), data, size) == 0) return TRUE; - } - - /* Even if we fail to parse, then some other element might succeed, so always - * put the VOS in the config */ - parse->profile = profile; - gst_mpeg4vparse_set_config (parse, data, size); - - parse->have_config = TRUE; - - /* Expect Visual Object startcode */ - GET_BITS (&bs, 32, &bits); - - /* but skip optional user data */ - if (!skip_user_data (&bs, &bits)) - goto failed; - - if (bits != VISUAL_OBJECT_STARTCODE_MARKER) - goto failed; - - GET_BITS (&bs, 1, &bits); - if (bits == 0x1) { - /* Skip visual_object_verid and priority */ - GET_BITS (&bs, 7, &bits); - } - - GET_BITS (&bs, 4, &bits); - /* Only support video ID */ - if (bits != 0x1) - goto failed; - - /* video signal type */ - GET_BITS (&bs, 1, &bits); - - if (bits == 0x1) { - /* video signal type, ignore format and range */ - GET_BITS (&bs, 4, &bits); - - GET_BITS (&bs, 1, &bits); - if (bits == 0x1) { - /* ignore color description */ - GET_BITS (&bs, 24, &bits); - } - } - - if (!next_start_code (&bs)) - goto failed; - - /* skip optional user data */ - GET_BITS (&bs, 32, &bits); - if (!skip_user_data (&bs, &bits)) - goto failed; - /* rewind to start code */ - bs.offset -= 4; - - data = &bs.data[bs.offset]; - size -= bs.offset; - - return gst_mpeg4vparse_handle_vo (parse, data, size, FALSE); - -out: - return gst_mpeg4vparse_set_new_caps (parse, 0, 0, -1, -1, -1, -1); - /* ERRORS */ -failed: - { - GST_WARNING_OBJECT (parse, "Failed to parse config data"); - goto out; - } -} - -static void -gst_mpeg4vparse_push (GstMpeg4VParse * parse, gsize size) -{ - if (G_UNLIKELY (!parse->have_config && parse->drop)) { - GST_LOG_OBJECT (parse, "Dropping %d bytes", parse->offset); - gst_adapter_flush (parse->adapter, size); - } else { - GstBuffer *out_buf; - - out_buf = gst_adapter_take_buffer (parse->adapter, parse->offset); - - if (G_LIKELY (out_buf)) { - out_buf = gst_buffer_make_metadata_writable (out_buf); - GST_BUFFER_TIMESTAMP (out_buf) = parse->timestamp; - - /* Set GST_BUFFER_FLAG_DELTA_UNIT if it's not an intra frame */ - if (!parse->intra_frame) { - GST_BUFFER_FLAG_SET (out_buf, GST_BUFFER_FLAG_DELTA_UNIT); - } else if (parse->interval > 0 && parse->config) { - GstClockTime timestamp = GST_BUFFER_TIMESTAMP (out_buf); - guint64 diff; - - /* init */ - if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (parse->last_report))) { - parse->last_report = timestamp; - } - - /* insert on intra frames */ - if (G_LIKELY (timestamp > parse->last_report)) - diff = timestamp - parse->last_report; - else - diff = 0; - - GST_LOG_OBJECT (parse, - "now %" GST_TIME_FORMAT ", last VOP-I %" GST_TIME_FORMAT, - GST_TIME_ARGS (timestamp), GST_TIME_ARGS (parse->last_report)); - - GST_DEBUG_OBJECT (parse, - "interval since last config %" GST_TIME_FORMAT, - GST_TIME_ARGS (diff)); - - if (G_UNLIKELY (GST_TIME_AS_SECONDS (diff) >= parse->interval)) { - /* we need to send config now first */ - GstBuffer *superbuf; - - GST_LOG_OBJECT (parse, "inserting config in stream"); - - /* insert header */ - superbuf = gst_buffer_merge (parse->config, out_buf); - gst_buffer_unref (out_buf); - - out_buf = gst_buffer_make_metadata_writable (superbuf); - GST_BUFFER_TIMESTAMP (out_buf) = timestamp; - - if (G_UNLIKELY (timestamp != -1)) { - parse->last_report = timestamp; - } - } - } - gst_buffer_set_caps (out_buf, GST_PAD_CAPS (parse->srcpad)); - gst_pad_push (parse->srcpad, out_buf); - } - } - - /* Restart now that we flushed data */ - parse->offset = 0; - parse->state = PARSE_NEED_START; - parse->intra_frame = FALSE; -} - -static GstFlowReturn -gst_mpeg4vparse_drain (GstMpeg4VParse * parse, GstBuffer * last_buffer) -{ - GstFlowReturn ret = GST_FLOW_OK; - const guint8 *data = NULL; - guint available = 0; - - available = gst_adapter_available (parse->adapter); - /* We do a quick check here to avoid the _peek() below. */ - if (G_UNLIKELY (available < 5)) { - GST_DEBUG_OBJECT (parse, "we need more data, %d < 5", available); - goto beach; - } - data = gst_adapter_peek (parse->adapter, available); - - /* Need at least 5 more bytes, 4 for the startcode, 1 to optionally determine - * the VOP frame type */ - while (available >= 5 && parse->offset < available - 5) { - if (data[parse->offset] == 0 && data[parse->offset + 1] == 0 && - data[parse->offset + 2] == 1) { - - switch (parse->state) { - case PARSE_NEED_START: - { - gboolean found = FALSE; - guint8 code; - - code = data[parse->offset + 3]; - - switch (code) { - case VOP_STARTCODE: - case VOS_STARTCODE: - case GOP_STARTCODE: - found = TRUE; - break; - default: - if (code <= 0x1f) - found = TRUE; - break; - } - if (found) { - /* valid starts of a frame */ - parse->state = PARSE_START_FOUND; - if (parse->offset > 0) { - GST_LOG_OBJECT (parse, "Flushing %u bytes", parse->offset); - gst_adapter_flush (parse->adapter, parse->offset); - parse->offset = 0; - available = gst_adapter_available (parse->adapter); - data = gst_adapter_peek (parse->adapter, available); - } - } else - parse->offset += 4; - break; - } - case PARSE_START_FOUND: - { - guint8 code; - - code = data[parse->offset + 3]; - - switch (code) { - case VOP_STARTCODE: - GST_LOG_OBJECT (parse, "found VOP start marker at %u", - parse->offset); - parse->intra_frame = ((data[parse->offset + 4] >> 6 & 0x3) == 0); - /* Ensure that the timestamp of the outgoing buffer is the same - * as the one the VOP header is found in */ - parse->timestamp = GST_BUFFER_TIMESTAMP (last_buffer); - parse->state = PARSE_VOP_FOUND; - break; - case VOS_STARTCODE: - GST_LOG_OBJECT (parse, "found VOS start marker at %u", - parse->offset); - parse->vos_offset = parse->offset; - parse->state = PARSE_VOS_FOUND; - break; - default: - if (code <= 0x1f) { - GST_LOG_OBJECT (parse, "found VO start marker at %u", - parse->offset); - parse->vos_offset = parse->offset; - parse->state = PARSE_VO_FOUND; - } - break; - } - /* Jump over it */ - parse->offset += 4; - break; - } - case PARSE_VO_FOUND: - switch (data[parse->offset + 3]) { - case GOP_STARTCODE: - case VOP_STARTCODE: - /* end of VOS found, interpret the config data and restart the - * search for the VOP */ - gst_mpeg4vparse_handle_vo (parse, data + parse->vos_offset, - parse->offset - parse->vos_offset, TRUE); - parse->state = PARSE_START_FOUND; - break; - default: - parse->offset += 4; - } - break; - case PARSE_VOS_FOUND: - switch (data[parse->offset + 3]) { - case GOP_STARTCODE: - case VOP_STARTCODE: - /* end of VOS found, interpret the config data and restart the - * search for the VOP */ - gst_mpeg4vparse_handle_vos (parse, data + parse->vos_offset, - parse->offset - parse->vos_offset); - parse->state = PARSE_START_FOUND; - break; - default: - parse->offset += 4; - } - break; - case PARSE_VOP_FOUND: - { /* We were in a VOP already, any start code marks the end of it */ - GST_LOG_OBJECT (parse, "found VOP end marker at %u", parse->offset); - - gst_mpeg4vparse_push (parse, parse->offset); - - available = gst_adapter_available (parse->adapter); - data = gst_adapter_peek (parse->adapter, available); - break; - } - default: - GST_WARNING_OBJECT (parse, "unexpected parse state (%d)", - parse->state); - ret = GST_FLOW_UNEXPECTED; - goto beach; - } - } else { /* Continue searching */ - parse->offset++; - } + if (!gst_mpeg4_params_parse_config (&mp4vparse->params, data, size)) { + GST_DEBUG_OBJECT (mp4vparse, "failed to parse config data (size %d)", size); + return FALSE; } -beach: - return ret; -} + GST_LOG_OBJECT (mp4vparse, "accepting parsed config size %d", size); -static GstFlowReturn -gst_mpeg4vparse_chain (GstPad * pad, GstBuffer * buffer) -{ - GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (gst_pad_get_parent (pad)); - GstFlowReturn ret = GST_FLOW_OK; - - GST_DEBUG_OBJECT (parse, "received buffer of %u bytes with ts %" - GST_TIME_FORMAT " and offset %" G_GINT64_FORMAT, GST_BUFFER_SIZE (buffer), - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), - GST_BUFFER_OFFSET (buffer)); - - gst_adapter_push (parse->adapter, buffer); + /* parsing ok, so accept it as new config */ + if (mp4vparse->config != NULL) + gst_buffer_unref (mp4vparse->config); - /* Drain the accumulated blocks frame per frame */ - ret = gst_mpeg4vparse_drain (parse, buffer); + mp4vparse->config = gst_buffer_new_and_alloc (size); + memcpy (GST_BUFFER_DATA (mp4vparse->config), data, size); - gst_object_unref (parse); + /* trigger src caps update */ + mp4vparse->update_caps = TRUE; - return ret; + return TRUE; } +/* caller guarantees at least start code in @buf at @off */ static gboolean -gst_mpeg4vparse_sink_setcaps (GstPad * pad, GstCaps * caps) +gst_mpeg4vparse_process_sc (GstMpeg4VParse * mp4vparse, GstBuffer * buf, + gint off) { - gboolean res = TRUE; - GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (gst_pad_get_parent (pad)); - GstStructure *s; - const GValue *value; + guint8 *data; + guint code; - GST_DEBUG_OBJECT (parse, "setcaps called with %" GST_PTR_FORMAT, caps); - parse->sink_caps = gst_caps_ref (caps); + g_return_val_if_fail (buf && GST_BUFFER_SIZE (buf) >= off + 4, FALSE); - s = gst_caps_get_structure (caps, 0); + data = GST_BUFFER_DATA (buf); + code = data[off + 3]; - if ((value = gst_structure_get_value (s, "codec_data")) != NULL - && G_VALUE_HOLDS (value, GST_TYPE_BUFFER)) { - GstBuffer *buf = gst_value_get_buffer (value); - - /* Set the config from this codec_data immediately so that in the worst - case, we don't just discard it. - Note that in most cases, this will be freed and overwritten when we - manage to parse the codec_data. */ - if (!parse->config) { - parse->config = gst_buffer_copy (buf); - } + GST_LOG_OBJECT (mp4vparse, "process startcode %x", code); - if (GST_BUFFER_SIZE (buf) < 4) { - GST_WARNING_OBJECT (parse, "codec_data too short, ignoring"); - goto failed_parse; + /* if we found a VOP, next start code ends it, + * except for final VOS end sequence code included in last VOP-frame */ + if (mp4vparse->vop_offset >= 0 && code != MPEG4_VOS_ENDCODE) { + if (G_LIKELY (GST_BUFFER_SIZE (buf) > mp4vparse->vop_offset + 4)) { + mp4vparse->intra_frame = + ((data[mp4vparse->vop_offset + 4] >> 6 & 0x3) == 0); } else { - const guint8 *data = GST_BUFFER_DATA (buf); - - res = FALSE; - if (data[0] == 0 && data[1] == 0 && data[2] == 1) { - if (data[3] == VOS_STARTCODE) { - /* Usually the codec data will be a visual object sequence, containing - a visual object, with a video object/video object layer. */ - res = gst_mpeg4vparse_handle_vos (parse, data, GST_BUFFER_SIZE (buf)); - } else if (data[3] <= VIDEO_OBJECT_STARTCODE_MAX) { - /* VIDEO_OBJECT_STARTCODE_MIN is zero, and data is unsigned, so we - don't need to check min (and in fact that causes a compile err */ - /* Sometimes, instead, it'll just have the video object/video object - layer data. We can parse that too, though it'll give us slightly - less information. */ - res = gst_mpeg4vparse_handle_vo (parse, data, GST_BUFFER_SIZE (buf), - FALSE); - } - if (!res) - goto failed_parse; - } else { - GST_WARNING_OBJECT (parse, - "codec_data does not begin with start code, invalid"); - goto failed_parse; - } + GST_WARNING_OBJECT (mp4vparse, "no data following VOP startcode"); + mp4vparse->intra_frame = FALSE; } - } else { - /* No codec data; treat the same a failed codec data */ - goto failed_parse; + GST_LOG_OBJECT (mp4vparse, "ending frame of size %d, is intra %d", off, + mp4vparse->intra_frame); + return TRUE; } -done: - gst_object_unref (parse); - return res; - -failed_parse: - /* No codec data, or obviously-invalid, so set minimal new caps. - VOS parsing later will (hopefully) fill in the other fields */ - res = gst_mpeg4vparse_set_new_caps (parse, 0, 0, 0, 0, 0, 0); - goto done; -} + switch (code) { + case MPEG4_VOP_STARTCODE: + case MPEG4_GOP_STARTCODE: + { + gint offset; -static gboolean -gst_mpeg4vparse_sink_event (GstPad * pad, GstEvent * event) -{ - gboolean res = TRUE; - GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (gst_pad_get_parent (pad)); - - GST_DEBUG_OBJECT (parse, "handling event type %s", - GST_EVENT_TYPE_NAME (event)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_FLUSH_STOP: - parse->last_report = GST_CLOCK_TIME_NONE; - gst_adapter_clear (parse->adapter); - parse->state = PARSE_NEED_START; - parse->offset = 0; - break; - case GST_EVENT_EOS: - if (parse->pending_segment != NULL) { - /* Send pending newsegment before EOS */ - gst_pad_push_event (parse->srcpad, parse->pending_segment); - parse->pending_segment = NULL; + if (code == MPEG4_VOP_STARTCODE) { + GST_LOG_OBJECT (mp4vparse, "startcode is VOP"); + mp4vparse->vop_offset = off; + } else { + GST_LOG_OBJECT (mp4vparse, "startcode is GOP"); } - if (parse->state == PARSE_VOP_FOUND) { - /* If we've found the start of the VOP assume what's left in the - * adapter is the complete VOP. This might cause us to send an - * incomplete VOP out, but prevents the last video frame from - * potentially being dropped */ - gst_mpeg4vparse_push (parse, gst_adapter_available (parse->adapter)); + /* parse config data ending here if proper startcodes found earlier; + * preferably start at VOS (visual object sequence), + * otherwise at VO (video object) */ + offset = mp4vparse->vos_offset >= 0 ? + mp4vparse->vos_offset : mp4vparse->vo_offset; + if (offset >= 0) { + gst_mpeg4vparse_process_config (mp4vparse, GST_BUFFER_DATA (buf), off); + /* avoid accepting again for a VOP sc following a GOP sc */ + mp4vparse->vos_offset = -1; + mp4vparse->vo_offset = -1; } - /* fallthrough */ - case GST_EVENT_FLUSH_START: - res = gst_pad_event_default (pad, event); break; - case GST_EVENT_NEWSEGMENT: - gst_event_replace (&parse->pending_segment, event); - gst_event_unref (event); - res = TRUE; + } + case MPEG4_VOS_STARTCODE: + GST_LOG_OBJECT (mp4vparse, "startcode is VOS"); + mp4vparse->vos_offset = off; break; default: - if (G_UNLIKELY (!parse->have_src_caps || parse->pending_segment)) { - /* We don't yet have enough data to set caps on the srcpad, so collect - * non-critical events till we do */ - parse->pending_events = g_list_append (parse->pending_events, event); - res = TRUE; - } else - res = gst_pad_event_default (pad, event); + /* VO (video object) cases */ + if (code <= 0x1f) { + GST_LOG_OBJECT (mp4vparse, "startcode is VO"); + mp4vparse->vo_offset = off; + } break; } - gst_object_unref (parse); - - return res; + /* at least need to have a VOP in a frame */ + return FALSE; } static gboolean -gst_mpeg4vparse_src_query (GstPad * pad, GstQuery * query) +gst_mpeg4vparse_check_valid_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, guint * framesize, gint * skipsize) { - GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (gst_pad_get_parent (pad)); - gboolean res; - - switch (GST_QUERY_TYPE (query)) { - case GST_QUERY_LATENCY: - { - /* We need to send the query upstream and add the returned latency to our - * own */ - GstClockTime min_latency, max_latency; - - gboolean us_live; - - GstClockTime our_latency; - - if ((res = gst_pad_peer_query (parse->sinkpad, query))) { - gst_query_parse_latency (query, &us_live, &min_latency, &max_latency); + GstMpeg4VParse *mp4vparse = GST_MPEG4VIDEOPARSE (parse); + GstBuffer *buf = frame->buffer; + GstByteReader reader = GST_BYTE_READER_INIT_FROM_BUFFER (buf); + gint off = 0; + gboolean ret; + guint code; + +retry: + /* at least start code and subsequent byte */ + if (G_UNLIKELY (GST_BUFFER_SIZE (buf) - off < 5)) + return FALSE; - GST_DEBUG_OBJECT (parse, "Peer latency: min %" - GST_TIME_FORMAT " max %" GST_TIME_FORMAT, - GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); + /* if already found a previous start code, e.g. start of frame, go for next */ + if (mp4vparse->last_sc >= 0) { + off = mp4vparse->last_sc; + goto next; + } - /* our latency is 1 frame, find the frame duration */ - our_latency = parse->frame_duration; + off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffff00, 0x00000100, + off, GST_BUFFER_SIZE (buf) - off); - GST_DEBUG_OBJECT (parse, "Our latency: %" GST_TIME_FORMAT, - GST_TIME_ARGS (our_latency)); + GST_LOG_OBJECT (mp4vparse, "possible sync at buffer offset %d", off); - /* we add some latency */ - min_latency += our_latency; - if (max_latency != -1) - max_latency += our_latency; + /* didn't find anything that looks like a sync word, skip */ + if (G_UNLIKELY (off < 0)) { + *skipsize = GST_BUFFER_SIZE (buf) - 3; + return FALSE; + } - GST_DEBUG_OBJECT (parse, "Calculated total latency : min %" - GST_TIME_FORMAT " max %" GST_TIME_FORMAT, - GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); + /* possible frame header, but not at offset 0? skip bytes before sync */ + if (G_UNLIKELY (off > 0)) { + *skipsize = off; + return FALSE; + } - gst_query_set_latency (query, us_live, min_latency, max_latency); - } + /* ensure start code looks like a real starting start code */ + code = GST_BUFFER_DATA (buf)[3]; + switch (code) { + case MPEG4_VOP_STARTCODE: + case MPEG4_VOS_STARTCODE: + case MPEG4_GOP_STARTCODE: break; - } default: - res = gst_pad_peer_query (parse->sinkpad, query); - break; + if (code <= 0x1f) + break; + /* undesirable sc */ + GST_LOG_OBJECT (mp4vparse, "start code is no VOS, VO, VOP or GOP"); + off++; + goto retry; + } + + /* found sc */ + mp4vparse->last_sc = 0; + + /* examine start code, which should not end frame at present */ + gst_mpeg4vparse_process_sc (mp4vparse, buf, 0); + +next: + /* start is fine as of now */ + *skipsize = 0; + /* position a bit further than last sc */ + off++; + /* so now we have start code at start of data; locate next start code */ + off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffff00, 0x00000100, + off, GST_BUFFER_SIZE (buf) - off); + + GST_LOG_OBJECT (mp4vparse, "next start code at %d", off); + if (off < 0) { + /* if draining, take all */ + if (GST_BASE_PARSE_DRAINING (parse)) { + off = GST_BUFFER_SIZE (buf); + ret = TRUE; + } else { + /* resume scan where we left it */ + mp4vparse->last_sc = GST_BUFFER_SIZE (buf) - 4; + gst_base_parse_set_min_frame_size (parse, GST_BUFFER_SIZE (buf) + 64); + return FALSE; + } + } else { + /* decide whether this startcode ends a frame */ + ret = gst_mpeg4vparse_process_sc (mp4vparse, buf, off); } - gst_object_unref (parse); - return res; + if (ret) { + *framesize = off; + gst_mpeg4vparse_reset_frame (mp4vparse); + } else { + goto next; + } + + return ret; } static void -gst_mpeg4vparse_cleanup (GstMpeg4VParse * parse) +gst_mpeg4vparse_update_src_caps (GstMpeg4VParse * mp4vparse) { - if (parse->sink_caps) { - gst_caps_unref (parse->sink_caps); - parse->sink_caps = NULL; - } - if (parse->adapter) { - gst_adapter_clear (parse->adapter); - } - if (parse->config != NULL) { - gst_buffer_unref (parse->config); - parse->config = NULL; - } - - if (parse->pending_segment) - gst_event_unref (parse->pending_segment); - parse->pending_segment = NULL; + GstCaps *caps = NULL; - g_list_foreach (parse->pending_events, (GFunc) gst_event_unref, NULL); - g_list_free (parse->pending_events); - parse->pending_events = NULL; + /* only update if no src caps yet or explicitly triggered */ + if (G_LIKELY (GST_PAD_CAPS (GST_BASE_PARSE_SRC_PAD (mp4vparse)) && + !mp4vparse->update_caps)) + return; - parse->have_src_caps = FALSE; + /* carry over input caps as much as possible; override with our own stuff */ + caps = GST_PAD_CAPS (GST_BASE_PARSE_SINK_PAD (mp4vparse)); + if (caps) { + caps = gst_caps_copy (caps); + } else { + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 4, NULL); + } - parse->state = PARSE_NEED_START; - parse->have_config = FALSE; - parse->offset = 0; - parse->last_report = GST_CLOCK_TIME_NONE; -} + gst_caps_set_simple (caps, "systemstream", G_TYPE_BOOLEAN, FALSE, + "parsed", G_TYPE_BOOLEAN, TRUE, NULL); -static GstStateChangeReturn -gst_mpeg4vparse_change_state (GstElement * element, GstStateChange transition) -{ - GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (element); + if (mp4vparse->profile != 0) { + gchar *profile = NULL; - GstStateChangeReturn ret; + /* FIXME does it make sense to expose the profile in the caps ? */ + profile = g_strdup_printf ("%d", mp4vparse->profile); + gst_caps_set_simple (caps, "profile-level-id", + G_TYPE_STRING, profile, NULL); + g_free (profile); + } - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (mp4vparse->config != NULL) { + gst_caps_set_simple (caps, "codec_data", + GST_TYPE_BUFFER, mp4vparse->config, NULL); + } - switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_READY: - gst_mpeg4vparse_cleanup (parse); - break; - default: - break; + if (mp4vparse->params.width > 0 && mp4vparse->params.height > 0) { + gst_caps_set_simple (caps, "width", G_TYPE_INT, mp4vparse->params.width, + "height", G_TYPE_INT, mp4vparse->params.height, NULL); } - return ret; -} -static void -gst_mpeg4vparse_finalize (GObject * object) -{ - GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (object); + /* perhaps we have a framerate */ + if (mp4vparse->params.fixed_time_increment != 0) { + gint fps_num = mp4vparse->params.time_increment_resolution; + gint fps_den = mp4vparse->params.fixed_time_increment; + GstClockTime latency = gst_util_uint64_scale (GST_SECOND, fps_den, fps_num); - gst_mpeg4vparse_cleanup (parse); + gst_caps_set_simple (caps, "framerate", + GST_TYPE_FRACTION, fps_num, fps_den, NULL); + gst_base_parse_set_frame_rate (GST_BASE_PARSE (mp4vparse), + fps_num, fps_den, 0, 0); + gst_base_parse_set_latency (GST_BASE_PARSE (mp4vparse), latency, latency); + } - if (parse->adapter) { - g_object_unref (parse->adapter); - parse->adapter = NULL; + /* or pixel-aspect-ratio */ + if (mp4vparse->params.aspect_ratio_width > 0 && + mp4vparse->params.aspect_ratio_height > 0) { + gst_caps_set_simple (caps, "pixel-aspect-ratio", + GST_TYPE_FRACTION, mp4vparse->params.aspect_ratio_width, + mp4vparse->params.aspect_ratio_height, NULL); } - GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); + gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (mp4vparse), caps); + gst_caps_unref (caps); } -static void -gst_mpeg4vparse_base_init (gpointer klass) +static GstFlowReturn +gst_mpeg4vparse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame) { - GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstMpeg4VParse *mp4vparse = GST_MPEG4VIDEOPARSE (parse); + GstBuffer *buffer = frame->buffer; - 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_mpeg4vparse_update_src_caps (mp4vparse); - gst_element_class_set_details_simple (element_class, - "MPEG 4 video elementary stream parser", "Codec/Parser/Video", - "Parses MPEG-4 Part 2 elementary video streams", - "Julien Moutte "); + if (mp4vparse->intra_frame) + GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + else + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + + if (G_UNLIKELY (mp4vparse->drop && !mp4vparse->config)) { + GST_DEBUG_OBJECT (mp4vparse, "dropping frame as no config yet"); + return GST_BASE_PARSE_FLOW_DROPPED; + } else + return GST_FLOW_OK; } -static void -gst_mpeg4vparse_set_property (GObject * object, guint property_id, - const GValue * value, GParamSpec * pspec) +static GstFlowReturn +gst_mpeg4vparse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) { - GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (object); + GstMpeg4VParse *mp4vparse = GST_MPEG4VIDEOPARSE (parse); + GstBuffer *buffer = frame->buffer; - switch (property_id) { - case PROP_DROP: - parse->drop = g_value_get_boolean (value); - break; - case PROP_CONFIG_INTERVAL: - parse->interval = g_value_get_uint (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - } -} + /* periodic SPS/PPS sending */ + if (mp4vparse->interval > 0) { + GstClockTime timestamp = GST_BUFFER_TIMESTAMP (buffer); + guint64 diff; -static void -gst_mpeg4vparse_get_property (GObject * object, guint property_id, - GValue * value, GParamSpec * pspec) -{ - GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (object); + /* init */ + if (!GST_CLOCK_TIME_IS_VALID (mp4vparse->last_report)) { + mp4vparse->last_report = timestamp; + } - switch (property_id) { - case PROP_DROP: - g_value_set_boolean (value, parse->drop); - break; - case PROP_CONFIG_INTERVAL: - g_value_set_uint (value, parse->interval); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - } -} + if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { + if (timestamp > mp4vparse->last_report) + diff = timestamp - mp4vparse->last_report; + else + diff = 0; -static void -gst_mpeg4vparse_class_init (GstMpeg4VParseClass * klass) -{ - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - gstelement_class = (GstElementClass *) klass; - gobject_class = G_OBJECT_CLASS (klass); + GST_LOG_OBJECT (mp4vparse, + "now %" GST_TIME_FORMAT ", last config %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp), GST_TIME_ARGS (mp4vparse->last_report)); - parent_class = g_type_class_peek_parent (klass); + GST_LOG_OBJECT (mp4vparse, + "interval since last config %" GST_TIME_FORMAT, GST_TIME_ARGS (diff)); - gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_mpeg4vparse_finalize); + if (GST_TIME_AS_SECONDS (diff) >= mp4vparse->interval) { + /* we need to send config now first */ + GST_LOG_OBJECT (parse, "inserting config in stream"); - gobject_class->set_property = gst_mpeg4vparse_set_property; - gobject_class->get_property = gst_mpeg4vparse_get_property; + /* avoid inserting duplicate config */ + if ((GST_BUFFER_SIZE (buffer) < GST_BUFFER_SIZE (mp4vparse->config)) || + memcmp (GST_BUFFER_DATA (buffer), + GST_BUFFER_DATA (mp4vparse->config), + GST_BUFFER_SIZE (mp4vparse->config))) { + GstBuffer *superbuf; - 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)); + /* insert header */ + superbuf = gst_buffer_merge (mp4vparse->config, buffer); + gst_buffer_copy_metadata (superbuf, buffer, GST_BUFFER_COPY_ALL); + gst_buffer_replace (&frame->buffer, superbuf); + gst_buffer_unref (superbuf); + } else { + GST_LOG_OBJECT (parse, "... but avoiding duplication"); + } - g_object_class_install_property (gobject_class, PROP_CONFIG_INTERVAL, - g_param_spec_uint ("config-interval", - "Configuration Send Interval", - "Send Configuration Insertion Interval in seconds (configuration headers " - "will be multiplexed in the data stream when detected.) (0 = disabled)", - 0, 3600, DEFAULT_CONFIG_INTERVAL, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + if (G_UNLIKELY (timestamp != -1)) { + mp4vparse->last_report = timestamp; + } + } + } + } - gstelement_class->change_state = - GST_DEBUG_FUNCPTR (gst_mpeg4vparse_change_state); + return GST_FLOW_OK; } -static void -gst_mpeg4vparse_init (GstMpeg4VParse * parse, GstMpeg4VParseClass * g_class) +static gboolean +gst_mpeg4vparse_set_caps (GstBaseParse * parse, GstCaps * caps) { - parse->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink"); - gst_pad_set_chain_function (parse->sinkpad, - GST_DEBUG_FUNCPTR (gst_mpeg4vparse_chain)); - gst_pad_set_event_function (parse->sinkpad, - GST_DEBUG_FUNCPTR (gst_mpeg4vparse_sink_event)); - gst_pad_set_setcaps_function (parse->sinkpad, - GST_DEBUG_FUNCPTR (gst_mpeg4vparse_sink_setcaps)); - gst_element_add_pad (GST_ELEMENT (parse), parse->sinkpad); - - parse->srcpad = gst_pad_new_from_static_template (&src_template, "src"); - gst_pad_set_query_function (parse->srcpad, - GST_DEBUG_FUNCPTR (gst_mpeg4vparse_src_query)); - gst_pad_use_fixed_caps (parse->srcpad); - gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad); - - parse->adapter = gst_adapter_new (); + GstMpeg4VParse *mp4vparse = GST_MPEG4VIDEOPARSE (parse); + GstStructure *s; + const GValue *value; + GstBuffer *buf; - parse->interval = DEFAULT_CONFIG_INTERVAL; - parse->last_report = GST_CLOCK_TIME_NONE; + GST_DEBUG_OBJECT (parse, "setcaps called with %" GST_PTR_FORMAT, caps); - gst_mpeg4vparse_cleanup (parse); + 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_mpeg4vparse_process_config (mp4vparse, GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf)); + } + + /* let's not interfere and accept regardless of config parsing success */ + return TRUE; } static gboolean diff --git a/gst/mpeg4videoparse/mpeg4videoparse.h b/gst/mpeg4videoparse/mpeg4videoparse.h index 29f7fa1..05d81e8 100644 --- a/gst/mpeg4videoparse/mpeg4videoparse.h +++ b/gst/mpeg4videoparse/mpeg4videoparse.h @@ -21,7 +21,9 @@ #define __MPEG4VIDEOPARSE_H__ #include -#include +#include + +#include "mpeg4parse.h" G_BEGIN_DECLS @@ -40,47 +42,30 @@ G_BEGIN_DECLS typedef struct _GstMpeg4VParse GstMpeg4VParse; typedef struct _GstMpeg4VParseClass GstMpeg4VParseClass; -typedef enum { - PARSE_NEED_START, - PARSE_START_FOUND, - PARSE_VO_FOUND, - PARSE_VOS_FOUND, - PARSE_VOP_FOUND -} GstMpeg4VParseState; - struct _GstMpeg4VParse { - GstElement element; - - GstPad * sinkpad; - GstPad * srcpad; - - GstCaps *sink_caps; + GstBaseParse element; - guint interval; GstClockTime last_report; - GstAdapter * adapter; - guint offset; - guint vos_offset; + /* parse state */ + gint last_sc; + gint vop_offset; + gint vos_offset; + gint vo_offset; gboolean intra_frame; - - GstMpeg4VParseState state; - GstClockTime timestamp; + gboolean update_caps; GstBuffer *config; - gboolean have_config; guint8 profile; - GstClockTime frame_duration; + MPEG4Params params; + /* properties */ gboolean drop; - - gboolean have_src_caps; - GstEvent *pending_segment; - GList *pending_events; + guint interval; }; struct _GstMpeg4VParseClass { - GstElementClass parent_class; + GstBaseParseClass parent_class; }; GType gst_mpeg4vparse_get_type (void); -- 2.7.4