| /* GStreamer |
| * Copyright (C) <2008> Mindfruit B.V. |
| * @author Sjoerd Simons <sjoerd@luon.net> |
| * Copyright (C) <2007> Julien Moutte <julien@fluendo.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| #include "mpeg4videoparse.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (mpeg4v_parse_debug); |
| #define GST_CAT_DEFAULT mpeg4v_parse_debug |
| |
| static GstStaticPadTemplate src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("video/mpeg, " |
| "mpegversion = (int) 4, " |
| "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) 4, " |
| "parsed = (boolean) false, " "systemstream = (boolean) false") |
| ); |
| |
| /* Properties */ |
| #define DEFAULT_PROP_DROP TRUE |
| #define DEFAULT_CONFIG_INTERVAL (0) |
| |
| enum |
| { |
| PROP_0, |
| PROP_DROP, |
| PROP_CONFIG_INTERVAL, |
| PROP_LAST |
| }; |
| |
| GST_BOILERPLATE (GstMpeg4VParse, gst_mpeg4vparse, GstElement, GST_TYPE_ELEMENT); |
| |
| 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); |
| |
| if (parse->profile != 0) { |
| gchar *profile = NULL; |
| |
| /* 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); |
| |
| 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; |
| |
| if (G_UNLIKELY (parse->pending_events != NULL)) { |
| GList *l; |
| |
| for (l = parse->pending_events; l != NULL; l = l->next) |
| gst_pad_push_event (parse->srcpad, GST_EVENT (l->data)); |
| |
| g_list_free (parse->pending_events); |
| parse->pending_events = NULL; |
| } |
| } |
| 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 |
| { |
| const guint8 *data; |
| /* byte offset */ |
| gsize offset; |
| /* bit offset */ |
| gsize b_offset; |
| |
| /* size in bytes */ |
| gsize size; |
| } bitstream_t; |
| |
| static gboolean |
| get_bits (bitstream_t * b, int num, guint32 * bits) |
| { |
| *bits = 0; |
| |
| if (b->offset + ((b->b_offset + num) / 8) > b->size) |
| return FALSE; |
| |
| if (b->b_offset + num <= 8) { |
| *bits = b->data[b->offset]; |
| *bits = (*bits >> (8 - num - b->b_offset)) & (((1 << num)) - 1); |
| |
| b->offset += (b->b_offset + num) / 8; |
| b->b_offset = (b->b_offset + num) % 8; |
| return TRUE; |
| } else { |
| /* going over the edge.. */ |
| int next; |
| |
| next = (8 - b->b_offset); |
| do { |
| guint32 t; |
| |
| if (!get_bits (b, next, &t)) |
| return FALSE; |
| *bits <<= next; |
| *bits |= t; |
| num -= next; |
| next = MIN (8, num); |
| } while (num > 0); |
| |
| return TRUE; |
| } |
| } |
| |
| #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) |
| { |
| 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; |
| } |
| |
| 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) |
| { |
| /* limit possible caps noise */ |
| if (parse->config && size == GST_BUFFER_SIZE (parse->config) && |
| memcmp (GST_BUFFER_DATA (parse->config), data, size) == 0) |
| return; |
| |
| if (parse->config != NULL) |
| gst_buffer_unref (parse->config); |
| |
| parse->config = gst_buffer_new_and_alloc (size); |
| memcpy (GST_BUFFER_DATA (parse->config), data, size); |
| } |
| |
| /* Handle parsing a video object */ |
| static gboolean |
| gst_mpeg4vparse_handle_vo (GstMpeg4VParse * parse, const guint8 * data, |
| gsize size, gboolean set_codec_data) |
| { |
| 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); |
| } |
| |
| /* 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]; |
| } |
| |
| 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; |
| |
| /* 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; |
| } |
| } |
| |
| static inline gboolean |
| skip_user_data (bitstream_t * bs, guint32 * bits) |
| { |
| while (*bits == USER_DATA_STARTCODE_MARKER) { |
| guint32 b; |
| |
| do { |
| GET_BITS (bs, 8, &b); |
| *bits = (*bits << 8) | b; |
| } while ((*bits >> 8) != START_MARKER); |
| } |
| |
| return TRUE; |
| |
| failed: |
| return FALSE; |
| } |
| |
| /* 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, |
| 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 |
| */ |
| 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++; |
| } |
| } |
| |
| beach: |
| return ret; |
| } |
| |
| 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); |
| |
| /* Drain the accumulated blocks frame per frame */ |
| ret = gst_mpeg4vparse_drain (parse, buffer); |
| |
| gst_object_unref (parse); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_mpeg4vparse_sink_setcaps (GstPad * pad, GstCaps * caps) |
| { |
| gboolean res = TRUE; |
| GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (gst_pad_get_parent (pad)); |
| GstStructure *s; |
| const GValue *value; |
| |
| GST_DEBUG_OBJECT (parse, "setcaps called with %" GST_PTR_FORMAT, caps); |
| parse->sink_caps = gst_caps_ref (caps); |
| |
| s = gst_caps_get_structure (caps, 0); |
| |
| 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); |
| } |
| |
| if (GST_BUFFER_SIZE (buf) < 4) { |
| GST_WARNING_OBJECT (parse, "codec_data too short, ignoring"); |
| goto failed_parse; |
| } 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; |
| } |
| } |
| } else { |
| /* No codec data; treat the same a failed codec data */ |
| goto failed_parse; |
| } |
| |
| 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; |
| } |
| |
| 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 (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)); |
| } |
| /* 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; |
| 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); |
| break; |
| } |
| |
| gst_object_unref (parse); |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_mpeg4vparse_src_query (GstPad * pad, GstQuery * query) |
| { |
| 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); |
| |
| GST_DEBUG_OBJECT (parse, "Peer latency: min %" |
| GST_TIME_FORMAT " max %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); |
| |
| /* our latency is 1 frame, find the frame duration */ |
| our_latency = parse->frame_duration; |
| |
| GST_DEBUG_OBJECT (parse, "Our latency: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (our_latency)); |
| |
| /* we add some latency */ |
| min_latency += our_latency; |
| if (max_latency != -1) |
| max_latency += our_latency; |
| |
| 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)); |
| |
| gst_query_set_latency (query, us_live, min_latency, max_latency); |
| } |
| break; |
| } |
| default: |
| res = gst_pad_peer_query (parse->sinkpad, query); |
| break; |
| } |
| gst_object_unref (parse); |
| |
| return res; |
| } |
| |
| static void |
| gst_mpeg4vparse_cleanup (GstMpeg4VParse * parse) |
| { |
| 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; |
| |
| g_list_foreach (parse->pending_events, (GFunc) gst_event_unref, NULL); |
| g_list_free (parse->pending_events); |
| parse->pending_events = NULL; |
| |
| parse->have_src_caps = FALSE; |
| |
| parse->state = PARSE_NEED_START; |
| parse->have_config = FALSE; |
| parse->offset = 0; |
| parse->last_report = GST_CLOCK_TIME_NONE; |
| } |
| |
| static GstStateChangeReturn |
| gst_mpeg4vparse_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (element); |
| |
| GstStateChangeReturn ret; |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| gst_mpeg4vparse_cleanup (parse); |
| break; |
| default: |
| break; |
| } |
| return ret; |
| } |
| |
| static void |
| gst_mpeg4vparse_finalize (GObject * object) |
| { |
| GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (object); |
| |
| gst_mpeg4vparse_cleanup (parse); |
| |
| if (parse->adapter) { |
| g_object_unref (parse->adapter); |
| parse->adapter = NULL; |
| } |
| |
| GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); |
| } |
| |
| static void |
| gst_mpeg4vparse_base_init (gpointer klass) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| |
| 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_details_simple (element_class, |
| "MPEG 4 video elementary stream parser", "Codec/Parser/Video", |
| "Parses MPEG-4 Part 2 elementary video streams", |
| "Julien Moutte <julien@fluendo.com>"); |
| } |
| |
| static void |
| gst_mpeg4vparse_set_property (GObject * object, guint property_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (object); |
| |
| 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); |
| } |
| } |
| |
| static void |
| gst_mpeg4vparse_get_property (GObject * object, guint property_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstMpeg4VParse *parse = GST_MPEG4VIDEOPARSE (object); |
| |
| 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 void |
| gst_mpeg4vparse_class_init (GstMpeg4VParseClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| gstelement_class = (GstElementClass *) klass; |
| gobject_class = G_OBJECT_CLASS (klass); |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_mpeg4vparse_finalize); |
| |
| gobject_class->set_property = gst_mpeg4vparse_set_property; |
| gobject_class->get_property = gst_mpeg4vparse_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_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)); |
| |
| gstelement_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_mpeg4vparse_change_state); |
| } |
| |
| static void |
| gst_mpeg4vparse_init (GstMpeg4VParse * parse, GstMpeg4VParseClass * g_class) |
| { |
| 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 (); |
| |
| parse->interval = DEFAULT_CONFIG_INTERVAL; |
| parse->last_report = GST_CLOCK_TIME_NONE; |
| |
| gst_mpeg4vparse_cleanup (parse); |
| } |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| GST_DEBUG_CATEGORY_INIT (mpeg4v_parse_debug, "mpeg4videoparse", 0, |
| "MPEG-4 video parser"); |
| |
| if (!gst_element_register (plugin, "mpeg4videoparse", GST_RANK_SECONDARY, |
| gst_mpeg4vparse_get_type ())) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| "mpeg4videoparse", |
| "MPEG-4 video parser", |
| plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |