| /* GStreamer RealMedia demuxer |
| * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> |
| * Copyright (C) <2003> David A. Schleef <ds@schleef.org> |
| * Copyright (C) <2004> Stephane Loeuillet <gstreamer@leroutier.net> |
| * Copyright (C) <2005> Owen Fraser-Green <owen@discobabe.net> |
| * Copyright (C) <2005> Michael Smith <fluendo.com> |
| * Copyright (C) <2006> Wim Taymans <wim@fluendo.com> |
| * Copyright (C) <2006> Tim-Philipp Müller <tim centricular net> |
| * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include "rmdemux.h" |
| #include "rmutils.h" |
| |
| #include <string.h> |
| #include <ctype.h> |
| |
| #define RMDEMUX_GUINT32_GET(a) GST_READ_UINT32_BE(a) |
| #define RMDEMUX_GUINT16_GET(a) GST_READ_UINT16_BE(a) |
| #define RMDEMUX_FOURCC_GET(a) GST_READ_UINT32_LE(a) |
| #define HEADER_SIZE 10 |
| #define DATA_SIZE 8 |
| |
| #define MAX_FRAGS 256 |
| |
| static const guint8 sipr_subpk_size[4] = { 29, 19, 37, 20 }; |
| |
| typedef struct _GstRMDemuxIndex GstRMDemuxIndex; |
| |
| struct _GstRMDemuxStream |
| { |
| guint32 subtype; |
| guint32 fourcc; |
| guint32 subformat; |
| guint32 format; |
| |
| int id; |
| GstPad *pad; |
| gboolean discont; |
| int timescale; |
| |
| int sample_index; |
| GstRMDemuxIndex *index; |
| int index_length; |
| gint framerate_numerator; |
| gint framerate_denominator; |
| guint32 seek_offset; |
| |
| guint16 width; |
| guint16 height; |
| guint16 flavor; |
| guint16 rate; /* samplerate */ |
| guint16 n_channels; /* channels */ |
| guint16 sample_width; /* bits_per_sample */ |
| guint16 leaf_size; /* subpacket_size */ |
| guint32 packet_size; /* coded_frame_size */ |
| guint16 version; |
| guint32 extra_data_size; /* codec_data_length */ |
| guint8 *extra_data; /* extras */ |
| guint32 bitrate; |
| |
| gboolean needs_descrambling; |
| guint subpackets_needed; /* subpackets needed for descrambling */ |
| GPtrArray *subpackets; /* array containing subpacket GstBuffers */ |
| |
| /* Variables needed for fixing timestamps. */ |
| GstClockTime next_ts, last_ts; |
| guint16 next_seq, last_seq; |
| |
| gint frag_seqnum; |
| gint frag_subseq; |
| guint frag_length; |
| guint frag_current; |
| guint frag_count; |
| guint frag_offset[MAX_FRAGS]; |
| GstAdapter *adapter; |
| |
| GstTagList *pending_tags; |
| }; |
| |
| struct _GstRMDemuxIndex |
| { |
| guint32 offset; |
| GstClockTime timestamp; |
| }; |
| |
| static GstStaticPadTemplate gst_rmdemux_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/vnd.rn-realmedia") |
| ); |
| |
| static GstStaticPadTemplate gst_rmdemux_videosrc_template = |
| GST_STATIC_PAD_TEMPLATE ("video_%u", |
| GST_PAD_SRC, |
| GST_PAD_SOMETIMES, |
| GST_STATIC_CAPS_ANY); |
| |
| static GstStaticPadTemplate gst_rmdemux_audiosrc_template = |
| GST_STATIC_PAD_TEMPLATE ("audio_%u", |
| GST_PAD_SRC, |
| GST_PAD_SOMETIMES, |
| GST_STATIC_CAPS_ANY); |
| |
| GST_DEBUG_CATEGORY_STATIC (rmdemux_debug); |
| #define GST_CAT_DEFAULT rmdemux_debug |
| |
| static GstElementClass *parent_class = NULL; |
| |
| static void gst_rmdemux_class_init (GstRMDemuxClass * klass); |
| static void gst_rmdemux_base_init (GstRMDemuxClass * klass); |
| static void gst_rmdemux_init (GstRMDemux * rmdemux); |
| static void gst_rmdemux_finalize (GObject * object); |
| static GstStateChangeReturn gst_rmdemux_change_state (GstElement * element, |
| GstStateChange transition); |
| static GstFlowReturn gst_rmdemux_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer); |
| static void gst_rmdemux_loop (GstPad * pad); |
| static gboolean gst_rmdemux_sink_activate (GstPad * sinkpad, |
| GstObject * parent); |
| static gboolean gst_rmdemux_sink_activate_mode (GstPad * sinkpad, |
| GstObject * parent, GstPadMode mode, gboolean active); |
| static gboolean gst_rmdemux_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_rmdemux_src_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static void gst_rmdemux_send_event (GstRMDemux * rmdemux, GstEvent * event); |
| static gboolean gst_rmdemux_src_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean gst_rmdemux_perform_seek (GstRMDemux * rmdemux, |
| GstEvent * event); |
| |
| static void gst_rmdemux_parse__rmf (GstRMDemux * rmdemux, const guint8 * data, |
| int length); |
| static void gst_rmdemux_parse_prop (GstRMDemux * rmdemux, const guint8 * data, |
| int length); |
| static void gst_rmdemux_parse_mdpr (GstRMDemux * rmdemux, |
| const guint8 * data, int length); |
| static guint gst_rmdemux_parse_indx (GstRMDemux * rmdemux, const guint8 * data, |
| int length); |
| static void gst_rmdemux_parse_data (GstRMDemux * rmdemux, const guint8 * data, |
| int length); |
| static void gst_rmdemux_parse_cont (GstRMDemux * rmdemux, const guint8 * data, |
| int length); |
| static GstFlowReturn gst_rmdemux_parse_packet (GstRMDemux * rmdemux, |
| GstBuffer * in, guint16 version); |
| static void gst_rmdemux_parse_indx_data (GstRMDemux * rmdemux, |
| const guint8 * data, int length); |
| static void gst_rmdemux_stream_clear_cached_subpackets (GstRMDemux * rmdemux, |
| GstRMDemuxStream * stream); |
| static GstRMDemuxStream *gst_rmdemux_get_stream_by_id (GstRMDemux * rmdemux, |
| int id); |
| |
| static GType |
| gst_rmdemux_get_type (void) |
| { |
| static GType rmdemux_type = 0; |
| |
| if (!rmdemux_type) { |
| static const GTypeInfo rmdemux_info = { |
| sizeof (GstRMDemuxClass), |
| (GBaseInitFunc) gst_rmdemux_base_init, NULL, |
| (GClassInitFunc) gst_rmdemux_class_init, |
| NULL, NULL, sizeof (GstRMDemux), 0, |
| (GInstanceInitFunc) gst_rmdemux_init, |
| }; |
| |
| rmdemux_type = |
| g_type_register_static (GST_TYPE_ELEMENT, "GstRMDemux", &rmdemux_info, |
| 0); |
| } |
| return rmdemux_type; |
| } |
| |
| static void |
| gst_rmdemux_base_init (GstRMDemuxClass * klass) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| |
| gst_element_class_add_static_pad_template (element_class, |
| &gst_rmdemux_sink_template); |
| gst_element_class_add_static_pad_template (element_class, |
| &gst_rmdemux_videosrc_template); |
| gst_element_class_add_static_pad_template (element_class, |
| &gst_rmdemux_audiosrc_template); |
| gst_element_class_set_static_metadata (element_class, "RealMedia Demuxer", |
| "Codec/Demuxer", |
| "Demultiplex a RealMedia file into audio and video streams", |
| "David Schleef <ds@schleef.org>"); |
| } |
| |
| static void |
| gst_rmdemux_class_init (GstRMDemuxClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| gstelement_class = (GstElementClass *) klass; |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_rmdemux_change_state); |
| |
| GST_DEBUG_CATEGORY_INIT (rmdemux_debug, "rmdemux", |
| 0, "Demuxer for Realmedia streams"); |
| |
| gobject_class->finalize = gst_rmdemux_finalize; |
| } |
| |
| static void |
| gst_rmdemux_finalize (GObject * object) |
| { |
| GstRMDemux *rmdemux = GST_RMDEMUX (object); |
| |
| if (rmdemux->adapter) { |
| g_object_unref (rmdemux->adapter); |
| rmdemux->adapter = NULL; |
| } |
| if (rmdemux->flowcombiner) { |
| gst_flow_combiner_free (rmdemux->flowcombiner); |
| rmdemux->flowcombiner = NULL; |
| } |
| |
| GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); |
| } |
| |
| static void |
| gst_rmdemux_init (GstRMDemux * rmdemux) |
| { |
| rmdemux->sinkpad = |
| gst_pad_new_from_static_template (&gst_rmdemux_sink_template, "sink"); |
| gst_pad_set_event_function (rmdemux->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_rmdemux_sink_event)); |
| gst_pad_set_chain_function (rmdemux->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_rmdemux_chain)); |
| gst_pad_set_activate_function (rmdemux->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_rmdemux_sink_activate)); |
| gst_pad_set_activatemode_function (rmdemux->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_rmdemux_sink_activate_mode)); |
| |
| gst_element_add_pad (GST_ELEMENT (rmdemux), rmdemux->sinkpad); |
| |
| rmdemux->adapter = gst_adapter_new (); |
| rmdemux->first_ts = GST_CLOCK_TIME_NONE; |
| rmdemux->base_ts = GST_CLOCK_TIME_NONE; |
| rmdemux->need_newsegment = TRUE; |
| rmdemux->have_group_id = FALSE; |
| rmdemux->group_id = G_MAXUINT; |
| rmdemux->flowcombiner = gst_flow_combiner_new (); |
| |
| gst_rm_utils_run_tests (); |
| } |
| |
| static gboolean |
| gst_rmdemux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| gboolean ret; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEGMENT: |
| gst_event_unref (event); |
| ret = TRUE; |
| break; |
| default: |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| return ret; |
| } |
| |
| static gboolean |
| gst_rmdemux_src_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| gboolean ret = TRUE; |
| |
| GstRMDemux *rmdemux = GST_RMDEMUX (parent); |
| |
| GST_LOG_OBJECT (rmdemux, "handling src event"); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEEK: |
| { |
| gboolean running; |
| |
| GST_LOG_OBJECT (rmdemux, "Event on src: SEEK"); |
| /* can't seek if we are not seekable, FIXME could pass the |
| * seek query upstream after converting it to bytes using |
| * the average bitrate of the stream. */ |
| if (!rmdemux->seekable) { |
| ret = FALSE; |
| GST_DEBUG ("seek on non seekable stream"); |
| goto done_unref; |
| } |
| |
| GST_OBJECT_LOCK (rmdemux); |
| /* check if we can do the seek now */ |
| running = rmdemux->running; |
| GST_OBJECT_UNLOCK (rmdemux); |
| |
| /* now do the seek */ |
| if (running) { |
| ret = gst_rmdemux_perform_seek (rmdemux, event); |
| } else |
| ret = TRUE; |
| |
| gst_event_unref (event); |
| break; |
| } |
| default: |
| GST_LOG_OBJECT (rmdemux, "Event on src: type=%d", GST_EVENT_TYPE (event)); |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| |
| return ret; |
| |
| done_unref: |
| GST_DEBUG ("error handling event"); |
| gst_event_unref (event); |
| return ret; |
| } |
| |
| /* Validate that this looks like a reasonable point to seek to */ |
| static gboolean |
| gst_rmdemux_validate_offset (GstRMDemux * rmdemux) |
| { |
| GstBuffer *buffer; |
| GstFlowReturn flowret; |
| guint16 version, length; |
| gboolean ret = TRUE; |
| GstMapInfo map; |
| |
| buffer = NULL; |
| flowret = gst_pad_pull_range (rmdemux->sinkpad, rmdemux->offset, 4, &buffer); |
| |
| if (flowret != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (rmdemux, "Failed to pull data at offset %d", |
| rmdemux->offset); |
| return FALSE; |
| } |
| /* TODO: Can we also be seeking to a 'DATA' chunk header? Check this. |
| * Also, for the case we currently handle, can we check any more? It's pretty |
| * sucky to not be validating a little more heavily than this... */ |
| /* This should now be the start of a data packet header. That begins with |
| * a 2-byte 'version' field, which has to be 0 or 1, then a length. I'm not |
| * certain what values are valid for length, but it must always be at least |
| * 4 bytes, and we can check that it won't take us past our known total size |
| */ |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| version = RMDEMUX_GUINT16_GET (map.data); |
| if (version != 0 && version != 1) { |
| GST_DEBUG_OBJECT (rmdemux, "Expected version 0 or 1, got %d", |
| (int) version); |
| ret = FALSE; |
| } |
| |
| length = RMDEMUX_GUINT16_GET (map.data + 2); |
| /* TODO: Also check against total stream length */ |
| if (length < 4) { |
| GST_DEBUG_OBJECT (rmdemux, "Expected length >= 4, got %d", (int) length); |
| ret = FALSE; |
| } |
| gst_buffer_unmap (buffer, &map); |
| |
| if (ret) { |
| rmdemux->offset += 4; |
| gst_adapter_clear (rmdemux->adapter); |
| gst_adapter_push (rmdemux->adapter, buffer); |
| } else { |
| GST_WARNING_OBJECT (rmdemux, "Failed to validate seek offset at %d", |
| rmdemux->offset); |
| gst_buffer_unref (buffer); |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| find_seek_offset_bytes (GstRMDemux * rmdemux, guint target) |
| { |
| int i; |
| GSList *cur; |
| gboolean ret = FALSE; |
| |
| for (cur = rmdemux->streams; cur; cur = cur->next) { |
| GstRMDemuxStream *stream = cur->data; |
| |
| /* Search backwards through this stream's index until we find the first |
| * timestamp before our target time */ |
| for (i = stream->index_length - 1; i >= 0; i--) { |
| if (stream->index[i].offset <= target) { |
| /* Set the seek_offset for the stream so we don't bother parsing it |
| * until we've passed that point */ |
| stream->seek_offset = stream->index[i].offset; |
| rmdemux->offset = stream->index[i].offset; |
| ret = TRUE; |
| break; |
| } |
| } |
| } |
| return ret; |
| } |
| |
| static gboolean |
| find_seek_offset_time (GstRMDemux * rmdemux, GstClockTime time) |
| { |
| int i, n_stream; |
| gboolean ret = FALSE; |
| GSList *cur; |
| GstClockTime earliest = GST_CLOCK_TIME_NONE; |
| |
| n_stream = 0; |
| for (cur = rmdemux->streams; cur; cur = cur->next, n_stream++) { |
| GstRMDemuxStream *stream = cur->data; |
| |
| /* Search backwards through this stream's index until we find the first |
| * timestamp before our target time */ |
| for (i = stream->index_length - 1; i >= 0; i--) { |
| if (stream->index[i].timestamp <= time) { |
| /* Set the seek_offset for the stream so we don't bother parsing it |
| * until we've passed that point */ |
| stream->seek_offset = stream->index[i].offset; |
| |
| /* If it's also the earliest timestamp we've seen of all streams, then |
| * that's our target! |
| */ |
| if (earliest == GST_CLOCK_TIME_NONE || |
| stream->index[i].timestamp < earliest) { |
| earliest = stream->index[i].timestamp; |
| rmdemux->offset = stream->index[i].offset; |
| GST_DEBUG_OBJECT (rmdemux, |
| "We're looking for %" GST_TIME_FORMAT |
| " and we found that stream %d has the latest index at %" |
| GST_TIME_FORMAT, GST_TIME_ARGS (rmdemux->segment.start), n_stream, |
| GST_TIME_ARGS (earliest)); |
| } |
| |
| ret = TRUE; |
| |
| break; |
| } |
| } |
| stream->discont = TRUE; |
| } |
| return ret; |
| } |
| |
| static gboolean |
| gst_rmdemux_perform_seek (GstRMDemux * rmdemux, GstEvent * event) |
| { |
| gboolean validated; |
| gboolean ret = TRUE; |
| gboolean flush; |
| GstFormat format; |
| gdouble rate; |
| GstSeekFlags flags; |
| GstSeekType cur_type, stop_type; |
| gint64 cur, stop; |
| gboolean update; |
| |
| if (event) { |
| GST_DEBUG_OBJECT (rmdemux, "seek with event"); |
| |
| gst_event_parse_seek (event, &rate, &format, &flags, |
| &cur_type, &cur, &stop_type, &stop); |
| |
| /* we can only seek on time */ |
| if (format != GST_FORMAT_TIME) { |
| GST_DEBUG_OBJECT (rmdemux, "can only seek on TIME"); |
| goto error; |
| } |
| /* cannot yet do backwards playback */ |
| if (rate <= 0.0) { |
| GST_DEBUG_OBJECT (rmdemux, "can only seek with positive rate, not %lf", |
| rate); |
| goto error; |
| } |
| } else { |
| GST_DEBUG_OBJECT (rmdemux, "seek without event"); |
| |
| flags = 0; |
| rate = 1.0; |
| } |
| |
| GST_DEBUG_OBJECT (rmdemux, "seek, rate %g", rate); |
| |
| flush = flags & GST_SEEK_FLAG_FLUSH; |
| |
| /* first step is to unlock the streaming thread if it is |
| * blocked in a chain call, we do this by starting the flush. */ |
| if (flush) { |
| gst_pad_push_event (rmdemux->sinkpad, gst_event_new_flush_start ()); |
| gst_rmdemux_send_event (rmdemux, gst_event_new_flush_start ()); |
| } else { |
| gst_pad_pause_task (rmdemux->sinkpad); |
| } |
| |
| GST_LOG_OBJECT (rmdemux, "Done starting flushes"); |
| |
| /* now grab the stream lock so that streaming cannot continue, for |
| * non flushing seeks when the element is in PAUSED this could block |
| * forever. */ |
| GST_PAD_STREAM_LOCK (rmdemux->sinkpad); |
| |
| GST_LOG_OBJECT (rmdemux, "Took streamlock"); |
| |
| if (event) { |
| gst_segment_do_seek (&rmdemux->segment, rate, format, flags, |
| cur_type, cur, stop_type, stop, &update); |
| } |
| |
| GST_DEBUG_OBJECT (rmdemux, "segment positions set to %" GST_TIME_FORMAT "-%" |
| GST_TIME_FORMAT, GST_TIME_ARGS (rmdemux->segment.start), |
| GST_TIME_ARGS (rmdemux->segment.stop)); |
| |
| /* we need to stop flushing on the sinkpad as we're going to use it |
| * next. We can do this as we have the STREAM lock now. */ |
| gst_pad_push_event (rmdemux->sinkpad, gst_event_new_flush_stop (TRUE)); |
| |
| GST_LOG_OBJECT (rmdemux, "Pushed FLUSH_STOP event"); |
| |
| /* For each stream, find the first index offset equal to or before our seek |
| * target. Of these, find the smallest offset. That's where we seek to. |
| * |
| * Then we pull 4 bytes from that offset, and validate that we've seeked to a |
| * what looks like a plausible packet. |
| * If that fails, restart, with the seek target set to one less than the |
| * offset we just tried. If we run out of places to try, treat that as a fatal |
| * error. |
| */ |
| if (!find_seek_offset_time (rmdemux, rmdemux->segment.position)) { |
| GST_LOG_OBJECT (rmdemux, "Failed to find seek offset by time"); |
| ret = FALSE; |
| goto done; |
| } |
| |
| GST_LOG_OBJECT (rmdemux, "Validating offset %u", rmdemux->offset); |
| validated = gst_rmdemux_validate_offset (rmdemux); |
| while (!validated) { |
| GST_INFO_OBJECT (rmdemux, "Failed to validate offset at %u", |
| rmdemux->offset); |
| if (!find_seek_offset_bytes (rmdemux, rmdemux->offset - 1)) { |
| ret = FALSE; |
| goto done; |
| } |
| validated = gst_rmdemux_validate_offset (rmdemux); |
| } |
| |
| GST_LOG_OBJECT (rmdemux, "Found final offset. Excellent!"); |
| |
| /* now we have a new position, prepare for streaming again */ |
| { |
| /* Reset the demuxer state */ |
| rmdemux->state = RMDEMUX_STATE_DATA_PACKET; |
| |
| if (flush) |
| gst_rmdemux_send_event (rmdemux, gst_event_new_flush_stop (TRUE)); |
| |
| /* must send newsegment event from streaming thread, so just set flag */ |
| rmdemux->need_newsegment = TRUE; |
| |
| /* notify start of new segment */ |
| if (rmdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) { |
| gst_element_post_message (GST_ELEMENT_CAST (rmdemux), |
| gst_message_new_segment_start (GST_OBJECT_CAST (rmdemux), |
| GST_FORMAT_TIME, rmdemux->segment.position)); |
| } |
| |
| /* restart our task since it might have been stopped when we did the |
| * flush. */ |
| gst_pad_start_task (rmdemux->sinkpad, (GstTaskFunction) gst_rmdemux_loop, |
| rmdemux->sinkpad, NULL); |
| } |
| |
| done: |
| /* streaming can continue now */ |
| GST_PAD_STREAM_UNLOCK (rmdemux->sinkpad); |
| |
| return ret; |
| |
| error: |
| { |
| GST_DEBUG_OBJECT (rmdemux, "seek failed"); |
| return FALSE; |
| } |
| } |
| |
| |
| static gboolean |
| gst_rmdemux_src_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| gboolean res = FALSE; |
| GstRMDemux *rmdemux; |
| |
| rmdemux = GST_RMDEMUX (parent); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_POSITION: |
| GST_DEBUG_OBJECT (rmdemux, "Position query: no idea from demuxer!"); |
| break; |
| case GST_QUERY_DURATION:{ |
| GstFormat fmt; |
| |
| gst_query_parse_duration (query, &fmt, NULL); |
| if (fmt == GST_FORMAT_TIME) { |
| GST_OBJECT_LOCK (rmdemux); |
| if (G_LIKELY (rmdemux->running)) { |
| gst_query_set_duration (query, GST_FORMAT_TIME, rmdemux->duration); |
| GST_DEBUG_OBJECT (rmdemux, "duration set to %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (rmdemux->duration)); |
| res = TRUE; |
| } |
| GST_OBJECT_UNLOCK (rmdemux); |
| } |
| break; |
| } |
| case GST_QUERY_SEEKING:{ |
| GstFormat fmt; |
| |
| gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); |
| if (fmt == GST_FORMAT_TIME) { |
| GST_OBJECT_LOCK (rmdemux); |
| if (G_LIKELY (rmdemux->running)) { |
| gst_query_set_seeking (query, GST_FORMAT_TIME, rmdemux->seekable, |
| 0, rmdemux->duration); |
| res = TRUE; |
| } |
| GST_OBJECT_UNLOCK (rmdemux); |
| } |
| break; |
| } |
| case GST_QUERY_SEGMENT: |
| { |
| GstFormat format; |
| gint64 start, stop; |
| |
| format = rmdemux->segment.format; |
| |
| start = |
| gst_segment_to_stream_time (&rmdemux->segment, format, |
| rmdemux->segment.start); |
| if ((stop = rmdemux->segment.stop) == -1) |
| stop = rmdemux->segment.duration; |
| else |
| stop = gst_segment_to_stream_time (&rmdemux->segment, format, stop); |
| |
| gst_query_set_segment (query, rmdemux->segment.rate, format, start, stop); |
| res = TRUE; |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, parent, query); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static void |
| gst_rmdemux_free_stream (GstRMDemux * rmdemux, GstRMDemuxStream * stream) |
| { |
| g_object_unref (stream->adapter); |
| gst_rmdemux_stream_clear_cached_subpackets (rmdemux, stream); |
| if (stream->pending_tags) |
| gst_tag_list_unref (stream->pending_tags); |
| if (stream->subpackets) |
| g_ptr_array_free (stream->subpackets, TRUE); |
| g_free (stream->index); |
| g_free (stream); |
| } |
| |
| static void |
| gst_rmdemux_reset (GstRMDemux * rmdemux) |
| { |
| GSList *cur; |
| |
| GST_OBJECT_LOCK (rmdemux); |
| rmdemux->running = FALSE; |
| GST_OBJECT_UNLOCK (rmdemux); |
| |
| for (cur = rmdemux->streams; cur; cur = cur->next) { |
| GstRMDemuxStream *stream = cur->data; |
| |
| gst_flow_combiner_remove_pad (rmdemux->flowcombiner, stream->pad); |
| gst_element_remove_pad (GST_ELEMENT (rmdemux), stream->pad); |
| gst_rmdemux_free_stream (rmdemux, stream); |
| } |
| g_slist_free (rmdemux->streams); |
| rmdemux->streams = NULL; |
| rmdemux->n_audio_streams = 0; |
| rmdemux->n_video_streams = 0; |
| |
| if (rmdemux->pending_tags != NULL) { |
| gst_tag_list_unref (rmdemux->pending_tags); |
| rmdemux->pending_tags = NULL; |
| } |
| |
| gst_adapter_clear (rmdemux->adapter); |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| rmdemux->have_pads = FALSE; |
| |
| gst_segment_init (&rmdemux->segment, GST_FORMAT_UNDEFINED); |
| rmdemux->first_ts = GST_CLOCK_TIME_NONE; |
| rmdemux->base_ts = GST_CLOCK_TIME_NONE; |
| rmdemux->need_newsegment = TRUE; |
| |
| rmdemux->have_group_id = FALSE; |
| rmdemux->group_id = G_MAXUINT; |
| } |
| |
| static GstStateChangeReturn |
| gst_rmdemux_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstRMDemux *rmdemux = GST_RMDEMUX (element); |
| GstStateChangeReturn res; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| break; |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| rmdemux->have_pads = FALSE; |
| gst_segment_init (&rmdemux->segment, GST_FORMAT_TIME); |
| rmdemux->running = FALSE; |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
| break; |
| default: |
| break; |
| } |
| |
| res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_READY:{ |
| gst_rmdemux_reset (rmdemux); |
| break; |
| } |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| break; |
| default: |
| break; |
| } |
| |
| return res; |
| } |
| |
| /* this function is called when the pad is activated and should start |
| * processing data. |
| * |
| * We check if we can do random access to decide if we work push or |
| * pull based. |
| */ |
| static gboolean |
| gst_rmdemux_sink_activate (GstPad * sinkpad, GstObject * parent) |
| { |
| GstQuery *query; |
| gboolean pull_mode; |
| |
| query = gst_query_new_scheduling (); |
| |
| if (!gst_pad_peer_query (sinkpad, query)) { |
| gst_query_unref (query); |
| goto activate_push; |
| } |
| |
| pull_mode = gst_query_has_scheduling_mode_with_flags (query, |
| GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE); |
| gst_query_unref (query); |
| |
| if (!pull_mode) |
| goto activate_push; |
| |
| GST_DEBUG_OBJECT (sinkpad, "activating pull"); |
| return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE); |
| |
| activate_push: |
| { |
| GST_DEBUG_OBJECT (sinkpad, "activating push"); |
| return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); |
| } |
| } |
| |
| static gboolean |
| gst_rmdemux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| gboolean res; |
| GstRMDemux *demux; |
| |
| demux = GST_RMDEMUX (parent); |
| |
| switch (mode) { |
| case GST_PAD_MODE_PUSH: |
| demux->seekable = FALSE; |
| demux->running = active; |
| res = TRUE; |
| break; |
| case GST_PAD_MODE_PULL: |
| if (active) { |
| demux->seekable = TRUE; |
| demux->offset = 0; |
| demux->loop_state = RMDEMUX_LOOP_STATE_HEADER; |
| demux->data_offset = G_MAXUINT; |
| res = |
| gst_pad_start_task (sinkpad, (GstTaskFunction) gst_rmdemux_loop, |
| sinkpad, NULL); |
| } else { |
| res = gst_pad_stop_task (sinkpad); |
| } |
| break; |
| default: |
| res = FALSE; |
| break; |
| } |
| return res; |
| } |
| |
| |
| /* random access mode - just pass over to our chain function */ |
| static void |
| gst_rmdemux_loop (GstPad * pad) |
| { |
| GstRMDemux *rmdemux; |
| GstBuffer *buffer; |
| GstFlowReturn ret = GST_FLOW_OK; |
| guint size; |
| |
| rmdemux = GST_RMDEMUX (GST_PAD_PARENT (pad)); |
| |
| GST_LOG_OBJECT (rmdemux, "loop with state=%d and offset=0x%x", |
| rmdemux->loop_state, rmdemux->offset); |
| |
| switch (rmdemux->state) { |
| case RMDEMUX_STATE_HEADER: |
| size = HEADER_SIZE; |
| break; |
| case RMDEMUX_STATE_HEADER_DATA: |
| size = DATA_SIZE; |
| break; |
| case RMDEMUX_STATE_DATA_PACKET: |
| size = rmdemux->avg_packet_size; |
| break; |
| case RMDEMUX_STATE_EOS: |
| GST_LOG_OBJECT (rmdemux, "At EOS, pausing task"); |
| ret = GST_FLOW_EOS; |
| goto need_pause; |
| default: |
| GST_LOG_OBJECT (rmdemux, "Default: requires %d bytes (state is %d)", |
| (int) rmdemux->size, rmdemux->state); |
| size = rmdemux->size; |
| } |
| |
| buffer = NULL; |
| ret = gst_pad_pull_range (pad, rmdemux->offset, size, &buffer); |
| if (ret != GST_FLOW_OK) { |
| if (rmdemux->offset == rmdemux->index_offset) { |
| /* The index isn't available so forget about it */ |
| rmdemux->loop_state = RMDEMUX_LOOP_STATE_DATA; |
| rmdemux->offset = rmdemux->data_offset; |
| GST_OBJECT_LOCK (rmdemux); |
| rmdemux->running = TRUE; |
| rmdemux->seekable = FALSE; |
| GST_OBJECT_UNLOCK (rmdemux); |
| return; |
| } else { |
| GST_DEBUG_OBJECT (rmdemux, "Unable to pull %d bytes at offset 0x%08x " |
| "(pull_range returned flow %s, state is %d)", (gint) size, |
| rmdemux->offset, gst_flow_get_name (ret), GST_STATE (rmdemux)); |
| goto need_pause; |
| } |
| } |
| |
| size = gst_buffer_get_size (buffer); |
| |
| /* Defer to the chain function */ |
| ret = gst_rmdemux_chain (pad, GST_OBJECT_CAST (rmdemux), buffer); |
| if (ret != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (rmdemux, "Chain flow failed at offset 0x%08x", |
| rmdemux->offset); |
| goto need_pause; |
| } |
| |
| rmdemux->offset += size; |
| |
| switch (rmdemux->loop_state) { |
| case RMDEMUX_LOOP_STATE_HEADER: |
| if (rmdemux->offset >= rmdemux->data_offset) { |
| /* It's the end of the header */ |
| rmdemux->loop_state = RMDEMUX_LOOP_STATE_INDEX; |
| rmdemux->offset = rmdemux->index_offset; |
| } |
| break; |
| case RMDEMUX_LOOP_STATE_INDEX: |
| if (rmdemux->state == RMDEMUX_STATE_HEADER) { |
| if (rmdemux->index_offset == 0) { |
| /* We've read the last index */ |
| rmdemux->loop_state = RMDEMUX_LOOP_STATE_DATA; |
| rmdemux->offset = rmdemux->data_offset; |
| GST_OBJECT_LOCK (rmdemux); |
| rmdemux->running = TRUE; |
| GST_OBJECT_UNLOCK (rmdemux); |
| } else { |
| /* Get the next index */ |
| rmdemux->offset = rmdemux->index_offset; |
| } |
| } |
| break; |
| case RMDEMUX_LOOP_STATE_DATA: |
| break; |
| } |
| |
| return; |
| |
| /* ERRORS */ |
| need_pause: |
| { |
| const gchar *reason = gst_flow_get_name (ret); |
| |
| GST_LOG_OBJECT (rmdemux, "pausing task, reason %s", reason); |
| rmdemux->segment_running = FALSE; |
| gst_pad_pause_task (rmdemux->sinkpad); |
| |
| if (ret == GST_FLOW_EOS) { |
| /* perform EOS logic */ |
| if (rmdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) { |
| gint64 stop; |
| |
| /* for segment playback we need to post when (in stream time) |
| * we stopped, this is either stop (when set) or the duration. */ |
| if ((stop = rmdemux->segment.stop) == -1) |
| stop = rmdemux->segment.duration; |
| |
| GST_LOG_OBJECT (rmdemux, "Sending segment done, at end of segment"); |
| gst_element_post_message (GST_ELEMENT (rmdemux), |
| gst_message_new_segment_done (GST_OBJECT (rmdemux), |
| GST_FORMAT_TIME, stop)); |
| gst_rmdemux_send_event (rmdemux, |
| gst_event_new_segment_done (GST_FORMAT_TIME, stop)); |
| } else { |
| /* normal playback, send EOS to all linked pads */ |
| GST_LOG_OBJECT (rmdemux, "Sending EOS, at end of stream"); |
| gst_rmdemux_send_event (rmdemux, gst_event_new_eos ()); |
| } |
| } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { |
| GST_ELEMENT_FLOW_ERROR (rmdemux, ret); |
| gst_rmdemux_send_event (rmdemux, gst_event_new_eos ()); |
| } |
| return; |
| } |
| } |
| |
| static gboolean |
| gst_rmdemux_fourcc_isplausible (guint32 fourcc) |
| { |
| int i; |
| |
| for (i = 0; i < 4; i++) { |
| if (!isprint ((int) ((unsigned char *) (&fourcc))[i])) { |
| return FALSE; |
| } |
| } |
| return TRUE; |
| } |
| |
| static GstFlowReturn |
| gst_rmdemux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| const guint8 *data; |
| guint16 version; |
| guint avail; |
| |
| GstRMDemux *rmdemux = GST_RMDEMUX (parent); |
| |
| if (rmdemux->base_ts == -1) { |
| if (GST_BUFFER_DTS_IS_VALID (buffer)) |
| rmdemux->base_ts = GST_BUFFER_DTS (buffer); |
| else |
| rmdemux->base_ts = GST_BUFFER_PTS (buffer); |
| |
| GST_LOG_OBJECT (rmdemux, "base_ts %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (rmdemux->base_ts)); |
| } |
| |
| gst_adapter_push (rmdemux->adapter, buffer); |
| |
| GST_LOG_OBJECT (rmdemux, "Chaining buffer of size %" G_GSIZE_FORMAT, |
| gst_buffer_get_size (buffer)); |
| |
| while (TRUE) { |
| avail = gst_adapter_available (rmdemux->adapter); |
| |
| GST_LOG_OBJECT (rmdemux, "looping in chain, avail %u", avail); |
| switch (rmdemux->state) { |
| case RMDEMUX_STATE_HEADER: |
| { |
| if (gst_adapter_available (rmdemux->adapter) < HEADER_SIZE) |
| goto unlock; |
| |
| data = gst_adapter_map (rmdemux->adapter, HEADER_SIZE); |
| |
| rmdemux->object_id = RMDEMUX_FOURCC_GET (data + 0); |
| rmdemux->size = RMDEMUX_GUINT32_GET (data + 4) - HEADER_SIZE; |
| rmdemux->object_version = RMDEMUX_GUINT16_GET (data + 8); |
| |
| /* Sanity-check. We assume that the FOURCC is printable ASCII */ |
| if (!gst_rmdemux_fourcc_isplausible (rmdemux->object_id)) { |
| /* Failed. Remain in HEADER state, try again... We flush only |
| * the actual FOURCC, not the entire header, because we could |
| * need to resync anywhere at all... really, this should never |
| * happen. */ |
| GST_WARNING_OBJECT (rmdemux, "Bogus looking header, unprintable " |
| "FOURCC"); |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, 4); |
| |
| break; |
| } |
| |
| GST_LOG_OBJECT (rmdemux, "header found with object_id=%" |
| GST_FOURCC_FORMAT |
| " size=%08x object_version=%d", |
| GST_FOURCC_ARGS (rmdemux->object_id), rmdemux->size, |
| rmdemux->object_version); |
| |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, HEADER_SIZE); |
| |
| switch (rmdemux->object_id) { |
| case GST_MAKE_FOURCC ('.', 'R', 'M', 'F'): |
| rmdemux->state = RMDEMUX_STATE_HEADER_RMF; |
| break; |
| case GST_MAKE_FOURCC ('P', 'R', 'O', 'P'): |
| rmdemux->state = RMDEMUX_STATE_HEADER_PROP; |
| break; |
| case GST_MAKE_FOURCC ('M', 'D', 'P', 'R'): |
| rmdemux->state = RMDEMUX_STATE_HEADER_MDPR; |
| break; |
| case GST_MAKE_FOURCC ('I', 'N', 'D', 'X'): |
| rmdemux->state = RMDEMUX_STATE_HEADER_INDX; |
| break; |
| case GST_MAKE_FOURCC ('D', 'A', 'T', 'A'): |
| rmdemux->state = RMDEMUX_STATE_HEADER_DATA; |
| break; |
| case GST_MAKE_FOURCC ('C', 'O', 'N', 'T'): |
| rmdemux->state = RMDEMUX_STATE_HEADER_CONT; |
| break; |
| default: |
| rmdemux->state = RMDEMUX_STATE_HEADER_UNKNOWN; |
| break; |
| } |
| break; |
| } |
| case RMDEMUX_STATE_HEADER_UNKNOWN: |
| { |
| if (gst_adapter_available (rmdemux->adapter) < rmdemux->size) |
| goto unlock; |
| |
| GST_WARNING_OBJECT (rmdemux, "Unknown object_id %" GST_FOURCC_FORMAT, |
| GST_FOURCC_ARGS (rmdemux->object_id)); |
| |
| gst_adapter_flush (rmdemux->adapter, rmdemux->size); |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| break; |
| } |
| case RMDEMUX_STATE_HEADER_RMF: |
| { |
| if (gst_adapter_available (rmdemux->adapter) < rmdemux->size) |
| goto unlock; |
| |
| if ((rmdemux->object_version == 0) || (rmdemux->object_version == 1)) { |
| data = gst_adapter_map (rmdemux->adapter, rmdemux->size); |
| gst_rmdemux_parse__rmf (rmdemux, data, rmdemux->size); |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, rmdemux->size); |
| } else { |
| gst_adapter_flush (rmdemux->adapter, rmdemux->size); |
| } |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| break; |
| } |
| case RMDEMUX_STATE_HEADER_PROP: |
| { |
| if (gst_adapter_available (rmdemux->adapter) < rmdemux->size) |
| goto unlock; |
| |
| data = gst_adapter_map (rmdemux->adapter, rmdemux->size); |
| gst_rmdemux_parse_prop (rmdemux, data, rmdemux->size); |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, rmdemux->size); |
| |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| break; |
| } |
| case RMDEMUX_STATE_HEADER_MDPR: |
| { |
| if (gst_adapter_available (rmdemux->adapter) < rmdemux->size) |
| goto unlock; |
| |
| data = gst_adapter_map (rmdemux->adapter, rmdemux->size); |
| gst_rmdemux_parse_mdpr (rmdemux, data, rmdemux->size); |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, rmdemux->size); |
| |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| break; |
| } |
| case RMDEMUX_STATE_HEADER_CONT: |
| { |
| if (gst_adapter_available (rmdemux->adapter) < rmdemux->size) |
| goto unlock; |
| |
| data = gst_adapter_map (rmdemux->adapter, rmdemux->size); |
| gst_rmdemux_parse_cont (rmdemux, data, rmdemux->size); |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, rmdemux->size); |
| |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| break; |
| } |
| case RMDEMUX_STATE_HEADER_DATA: |
| { |
| /* If we haven't already done so then signal there are no more pads */ |
| if (!rmdemux->have_pads) { |
| GST_LOG_OBJECT (rmdemux, "no more pads"); |
| gst_element_no_more_pads (GST_ELEMENT (rmdemux)); |
| rmdemux->have_pads = TRUE; |
| } |
| |
| /* The actual header is only 8 bytes */ |
| rmdemux->size = DATA_SIZE; |
| GST_LOG_OBJECT (rmdemux, "data available %" G_GSIZE_FORMAT, |
| gst_adapter_available (rmdemux->adapter)); |
| if (gst_adapter_available (rmdemux->adapter) < rmdemux->size) |
| goto unlock; |
| |
| data = gst_adapter_map (rmdemux->adapter, rmdemux->size); |
| gst_rmdemux_parse_data (rmdemux, data, rmdemux->size); |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, rmdemux->size); |
| |
| rmdemux->state = RMDEMUX_STATE_DATA_PACKET; |
| break; |
| } |
| case RMDEMUX_STATE_HEADER_INDX: |
| { |
| if (gst_adapter_available (rmdemux->adapter) < rmdemux->size) |
| goto unlock; |
| |
| data = gst_adapter_map (rmdemux->adapter, rmdemux->size); |
| rmdemux->size = gst_rmdemux_parse_indx (rmdemux, data, rmdemux->size); |
| /* Only flush the header */ |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, HEADER_SIZE); |
| |
| rmdemux->state = RMDEMUX_STATE_INDX_DATA; |
| break; |
| } |
| case RMDEMUX_STATE_INDX_DATA: |
| { |
| /* There's not always an data to get... */ |
| if (rmdemux->size > 0) { |
| if (gst_adapter_available (rmdemux->adapter) < rmdemux->size) |
| goto unlock; |
| |
| data = gst_adapter_map (rmdemux->adapter, rmdemux->size); |
| gst_rmdemux_parse_indx_data (rmdemux, data, rmdemux->size); |
| gst_adapter_unmap (rmdemux->adapter); |
| gst_adapter_flush (rmdemux->adapter, rmdemux->size); |
| } |
| |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| break; |
| } |
| case RMDEMUX_STATE_DATA_PACKET: |
| { |
| guint8 header[4]; |
| |
| if (gst_adapter_available (rmdemux->adapter) < 2) |
| goto unlock; |
| |
| gst_adapter_copy (rmdemux->adapter, header, 0, 2); |
| version = RMDEMUX_GUINT16_GET (header); |
| GST_LOG_OBJECT (rmdemux, "Data packet with version=%d", version); |
| |
| if (version == 0 || version == 1) { |
| guint16 length; |
| |
| if (gst_adapter_available (rmdemux->adapter) < 4) |
| goto unlock; |
| |
| gst_adapter_copy (rmdemux->adapter, header, 0, 4); |
| |
| length = RMDEMUX_GUINT16_GET (header + 2); |
| GST_LOG_OBJECT (rmdemux, "Got length %d", length); |
| |
| if (length < 4) { |
| GST_LOG_OBJECT (rmdemux, "length too small, dropping"); |
| /* Invalid, just drop it */ |
| gst_adapter_flush (rmdemux->adapter, 4); |
| } else { |
| GstBuffer *buffer; |
| |
| avail = gst_adapter_available (rmdemux->adapter); |
| if (avail < length) |
| goto unlock; |
| |
| GST_LOG_OBJECT (rmdemux, "we have %u available and we needed %d", |
| avail, length); |
| |
| /* flush version and length */ |
| gst_adapter_flush (rmdemux->adapter, 4); |
| length -= 4; |
| |
| buffer = gst_adapter_take_buffer (rmdemux->adapter, length); |
| |
| ret = gst_rmdemux_parse_packet (rmdemux, buffer, version); |
| rmdemux->chunk_index++; |
| } |
| |
| if (rmdemux->chunk_index == rmdemux->n_chunks || length == 0) |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| } else { |
| /* Stream done */ |
| gst_adapter_flush (rmdemux->adapter, 2); |
| |
| if (rmdemux->data_offset == 0) { |
| GST_LOG_OBJECT (rmdemux, |
| "No further data, internal demux state EOS"); |
| rmdemux->state = RMDEMUX_STATE_EOS; |
| } else |
| rmdemux->state = RMDEMUX_STATE_HEADER; |
| } |
| break; |
| } |
| case RMDEMUX_STATE_EOS: |
| gst_rmdemux_send_event (rmdemux, gst_event_new_eos ()); |
| goto unlock; |
| default: |
| GST_WARNING_OBJECT (rmdemux, "Unhandled state %d", rmdemux->state); |
| goto unlock; |
| } |
| } |
| |
| unlock: |
| return ret; |
| } |
| |
| static GstRMDemuxStream * |
| gst_rmdemux_get_stream_by_id (GstRMDemux * rmdemux, int id) |
| { |
| GSList *cur; |
| |
| for (cur = rmdemux->streams; cur; cur = cur->next) { |
| GstRMDemuxStream *stream = cur->data; |
| |
| if (stream->id == id) { |
| return stream; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| gst_rmdemux_send_event (GstRMDemux * rmdemux, GstEvent * event) |
| { |
| GSList *cur; |
| |
| for (cur = rmdemux->streams; cur; cur = cur->next) { |
| GstRMDemuxStream *stream = cur->data; |
| |
| GST_DEBUG_OBJECT (rmdemux, "Pushing %s event on pad %s", |
| GST_EVENT_TYPE_NAME (event), GST_PAD_NAME (stream->pad)); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_STOP: |
| stream->last_ts = -1; |
| stream->next_ts = -1; |
| stream->last_seq = -1; |
| stream->next_seq = -1; |
| break; |
| default: |
| break; |
| } |
| gst_event_ref (event); |
| gst_pad_push_event (stream->pad, event); |
| } |
| gst_event_unref (event); |
| } |
| |
| static void |
| gst_rmdemux_add_stream (GstRMDemux * rmdemux, GstRMDemuxStream * stream) |
| { |
| GstCaps *stream_caps = NULL; |
| const gchar *codec_tag = NULL; |
| gchar *codec_name = NULL; |
| gchar *stream_id; |
| int version = 0; |
| |
| if (stream->subtype == GST_RMDEMUX_STREAM_VIDEO) { |
| char *name = g_strdup_printf ("video_%u", rmdemux->n_video_streams); |
| |
| stream->pad = |
| gst_pad_new_from_static_template (&gst_rmdemux_videosrc_template, name); |
| g_free (name); |
| |
| codec_tag = GST_TAG_VIDEO_CODEC; |
| |
| switch (stream->fourcc) { |
| case GST_RM_VDO_RV10: |
| version = 1; |
| break; |
| case GST_RM_VDO_RV20: |
| version = 2; |
| break; |
| case GST_RM_VDO_RV30: |
| version = 3; |
| break; |
| case GST_RM_VDO_RV40: |
| version = 4; |
| break; |
| default: |
| stream_caps = gst_caps_new_simple ("video/x-unknown-fourcc", |
| "fourcc", G_TYPE_UINT, stream->fourcc, NULL); |
| GST_WARNING_OBJECT (rmdemux, |
| "Unknown video FOURCC code \"%" GST_FOURCC_FORMAT "\" (%08x)", |
| GST_FOURCC_ARGS (stream->fourcc), stream->fourcc); |
| } |
| |
| if (version) { |
| stream_caps = |
| gst_caps_new_simple ("video/x-pn-realvideo", "rmversion", G_TYPE_INT, |
| (int) version, |
| "format", G_TYPE_INT, |
| (int) stream->format, |
| "subformat", G_TYPE_INT, (int) stream->subformat, NULL); |
| } |
| |
| if (stream_caps) { |
| gst_caps_set_simple (stream_caps, |
| "width", G_TYPE_INT, stream->width, |
| "height", G_TYPE_INT, stream->height, |
| "framerate", GST_TYPE_FRACTION, stream->framerate_numerator, |
| stream->framerate_denominator, NULL); |
| } |
| rmdemux->n_video_streams++; |
| |
| } else if (stream->subtype == GST_RMDEMUX_STREAM_AUDIO) { |
| char *name = g_strdup_printf ("audio_%u", rmdemux->n_audio_streams); |
| |
| stream->pad = |
| gst_pad_new_from_static_template (&gst_rmdemux_audiosrc_template, name); |
| GST_LOG_OBJECT (rmdemux, "Created audio pad \"%s\"", name); |
| g_free (name); |
| |
| codec_tag = GST_TAG_AUDIO_CODEC; |
| |
| switch (stream->fourcc) { |
| /* Older RealAudio Codecs */ |
| case GST_RM_AUD_14_4: |
| version = 1; |
| break; |
| |
| case GST_RM_AUD_28_8: |
| version = 2; |
| break; |
| |
| /* DolbyNet (Dolby AC3, low bitrate) */ |
| case GST_RM_AUD_DNET: |
| stream_caps = |
| gst_caps_new_simple ("audio/x-ac3", "rate", G_TYPE_INT, |
| (int) stream->rate, NULL); |
| stream->needs_descrambling = TRUE; |
| stream->subpackets_needed = 1; |
| stream->subpackets = NULL; |
| break; |
| |
| /* MPEG-4 based */ |
| case GST_RM_AUD_RAAC: |
| case GST_RM_AUD_RACP: |
| stream_caps = |
| gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, |
| (int) 4, "framed", G_TYPE_BOOLEAN, TRUE, NULL); |
| if (stream->extra_data_size > 0) { |
| /* strip off an unknown byte in the extra data */ |
| stream->extra_data_size--; |
| stream->extra_data++; |
| } |
| stream->needs_descrambling = TRUE; |
| stream->subpackets_needed = 1; |
| stream->subpackets = NULL; |
| break; |
| |
| /* Sony ATRAC3 */ |
| case GST_RM_AUD_ATRC: |
| stream_caps = gst_caps_new_empty_simple ("audio/x-vnd.sony.atrac3"); |
| stream->needs_descrambling = TRUE; |
| stream->subpackets_needed = stream->height; |
| stream->subpackets = NULL; |
| break; |
| |
| /* RealAudio G2 audio */ |
| case GST_RM_AUD_COOK: |
| version = 8; |
| stream->needs_descrambling = TRUE; |
| stream->subpackets_needed = stream->height; |
| stream->subpackets = NULL; |
| break; |
| |
| /* RALF is lossless */ |
| case GST_RM_AUD_RALF: |
| GST_DEBUG_OBJECT (rmdemux, "RALF"); |
| stream_caps = gst_caps_new_empty_simple ("audio/x-ralf-mpeg4-generic"); |
| break; |
| |
| case GST_RM_AUD_SIPR: |
| |
| if (stream->flavor > 3) { |
| GST_WARNING_OBJECT (rmdemux, "bad SIPR flavor %d, freeing it", |
| stream->flavor); |
| g_object_unref (stream->pad); |
| gst_rmdemux_free_stream (rmdemux, stream); |
| goto beach; |
| } |
| |
| GST_DEBUG_OBJECT (rmdemux, "SIPR"); |
| stream_caps = gst_caps_new_empty_simple ("audio/x-sipro"); |
| stream->needs_descrambling = TRUE; |
| stream->subpackets_needed = stream->height; |
| stream->subpackets = NULL; |
| stream->leaf_size = sipr_subpk_size[stream->flavor]; |
| |
| break; |
| |
| default: |
| stream_caps = gst_caps_new_simple ("video/x-unknown-fourcc", |
| "fourcc", G_TYPE_UINT, stream->fourcc, NULL); |
| GST_WARNING_OBJECT (rmdemux, |
| "Unknown audio FOURCC code \"%" GST_FOURCC_FORMAT "\" (%08x)", |
| GST_FOURCC_ARGS (stream->fourcc), stream->fourcc); |
| break; |
| } |
| |
| if (version) { |
| stream_caps = |
| gst_caps_new_simple ("audio/x-pn-realaudio", "raversion", G_TYPE_INT, |
| (int) version, NULL); |
| } |
| |
| if (stream_caps) { |
| gst_caps_set_simple (stream_caps, |
| "flavor", G_TYPE_INT, (int) stream->flavor, |
| "rate", G_TYPE_INT, (int) stream->rate, |
| "channels", G_TYPE_INT, (int) stream->n_channels, |
| "width", G_TYPE_INT, (int) stream->sample_width, |
| "leaf_size", G_TYPE_INT, (int) stream->leaf_size, |
| "packet_size", G_TYPE_INT, (int) stream->packet_size, |
| "bitrate", G_TYPE_INT, (int) stream->bitrate, |
| "height", G_TYPE_INT, (int) stream->height, NULL); |
| } |
| rmdemux->n_audio_streams++; |
| } else { |
| GST_WARNING_OBJECT (rmdemux, "not adding stream of type %d, freeing it", |
| stream->subtype); |
| gst_rmdemux_free_stream (rmdemux, stream); |
| goto beach; |
| } |
| |
| GST_PAD_ELEMENT_PRIVATE (stream->pad) = stream; |
| rmdemux->streams = g_slist_append (rmdemux->streams, stream); |
| GST_LOG_OBJECT (rmdemux, "n_streams is now %d", |
| g_slist_length (rmdemux->streams)); |
| |
| GST_LOG ("stream->pad = %p, stream_caps = %" GST_PTR_FORMAT, stream->pad, |
| stream_caps); |
| |
| if (stream->pad && stream_caps) { |
| GstEvent *event; |
| |
| GST_LOG_OBJECT (rmdemux, "%d bytes of extra data for stream %s", |
| stream->extra_data_size, GST_PAD_NAME (stream->pad)); |
| |
| /* add codec_data if there is any */ |
| if (stream->extra_data_size > 0) { |
| GstBuffer *buffer; |
| |
| buffer = gst_buffer_new_and_alloc (stream->extra_data_size); |
| gst_buffer_fill (buffer, 0, stream->extra_data, stream->extra_data_size); |
| |
| gst_caps_set_simple (stream_caps, "codec_data", GST_TYPE_BUFFER, |
| buffer, NULL); |
| |
| gst_buffer_unref (buffer); |
| } |
| |
| gst_pad_use_fixed_caps (stream->pad); |
| |
| gst_pad_set_event_function (stream->pad, |
| GST_DEBUG_FUNCPTR (gst_rmdemux_src_event)); |
| gst_pad_set_query_function (stream->pad, |
| GST_DEBUG_FUNCPTR (gst_rmdemux_src_query)); |
| |
| GST_DEBUG_OBJECT (rmdemux, "adding pad %s with caps %" GST_PTR_FORMAT |
| ", stream_id=%d", GST_PAD_NAME (stream->pad), stream_caps, stream->id); |
| gst_pad_set_active (stream->pad, TRUE); |
| |
| stream_id = |
| gst_pad_create_stream_id_printf (stream->pad, |
| GST_ELEMENT_CAST (rmdemux), "%03u", stream->id); |
| |
| event = |
| gst_pad_get_sticky_event (rmdemux->sinkpad, GST_EVENT_STREAM_START, 0); |
| if (event) { |
| if (gst_event_parse_group_id (event, &rmdemux->group_id)) |
| rmdemux->have_group_id = TRUE; |
| else |
| rmdemux->have_group_id = FALSE; |
| gst_event_unref (event); |
| } else if (!rmdemux->have_group_id) { |
| rmdemux->have_group_id = TRUE; |
| rmdemux->group_id = gst_util_group_id_next (); |
| } |
| |
| event = gst_event_new_stream_start (stream_id); |
| if (rmdemux->have_group_id) |
| gst_event_set_group_id (event, rmdemux->group_id); |
| |
| gst_pad_push_event (stream->pad, event); |
| g_free (stream_id); |
| |
| gst_pad_set_caps (stream->pad, stream_caps); |
| |
| codec_name = gst_pb_utils_get_codec_description (stream_caps); |
| |
| /* save for later, we must send the tags after the newsegment event */ |
| if (codec_tag != NULL && codec_name != NULL) { |
| if (stream->pending_tags == NULL) |
| stream->pending_tags = gst_tag_list_new_empty (); |
| gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_KEEP, |
| codec_tag, codec_name, NULL); |
| g_free (codec_name); |
| } |
| gst_element_add_pad (GST_ELEMENT_CAST (rmdemux), stream->pad); |
| gst_flow_combiner_add_pad (rmdemux->flowcombiner, stream->pad); |
| } |
| |
| beach: |
| |
| if (stream_caps) |
| gst_caps_unref (stream_caps); |
| } |
| |
| static int |
| re_skip_pascal_string (const guint8 * ptr) |
| { |
| int length; |
| |
| length = ptr[0]; |
| |
| return length + 1; |
| } |
| |
| static void |
| gst_rmdemux_parse__rmf (GstRMDemux * rmdemux, const guint8 * data, int length) |
| { |
| GST_LOG_OBJECT (rmdemux, "file_version: %d", RMDEMUX_GUINT32_GET (data)); |
| GST_LOG_OBJECT (rmdemux, "num_headers: %d", RMDEMUX_GUINT32_GET (data + 4)); |
| } |
| |
| static void |
| gst_rmdemux_parse_prop (GstRMDemux * rmdemux, const guint8 * data, int length) |
| { |
| GST_LOG_OBJECT (rmdemux, "max bitrate: %d", RMDEMUX_GUINT32_GET (data)); |
| GST_LOG_OBJECT (rmdemux, "avg bitrate: %d", RMDEMUX_GUINT32_GET (data + 4)); |
| GST_LOG_OBJECT (rmdemux, "max packet size: %d", |
| RMDEMUX_GUINT32_GET (data + 8)); |
| rmdemux->avg_packet_size = RMDEMUX_GUINT32_GET (data + 12); |
| GST_LOG_OBJECT (rmdemux, "avg packet size: %d", rmdemux->avg_packet_size); |
| rmdemux->num_packets = RMDEMUX_GUINT32_GET (data + 16); |
| GST_LOG_OBJECT (rmdemux, "number of packets: %d", rmdemux->num_packets); |
| |
| GST_LOG_OBJECT (rmdemux, "duration: %d", RMDEMUX_GUINT32_GET (data + 20)); |
| rmdemux->duration = RMDEMUX_GUINT32_GET (data + 20) * GST_MSECOND; |
| |
| GST_LOG_OBJECT (rmdemux, "preroll: %d", RMDEMUX_GUINT32_GET (data + 24)); |
| rmdemux->index_offset = RMDEMUX_GUINT32_GET (data + 28); |
| GST_LOG_OBJECT (rmdemux, "offset of INDX section: 0x%08x", |
| rmdemux->index_offset); |
| rmdemux->data_offset = RMDEMUX_GUINT32_GET (data + 32); |
| GST_LOG_OBJECT (rmdemux, "offset of DATA section: 0x%08x", |
| rmdemux->data_offset); |
| GST_LOG_OBJECT (rmdemux, "n streams: %d", RMDEMUX_GUINT16_GET (data + 36)); |
| GST_LOG_OBJECT (rmdemux, "flags: 0x%04x", RMDEMUX_GUINT16_GET (data + 38)); |
| } |
| |
| static void |
| gst_rmdemux_parse_mdpr (GstRMDemux * rmdemux, const guint8 * data, int length) |
| { |
| GstRMDemuxStream *stream; |
| char *stream1_type_string; |
| char *stream2_type_string; |
| guint str_len = 0; |
| int stream_type; |
| int offset; |
| guint32 max_bitrate; |
| guint32 avg_bitrate; |
| |
| stream = g_new0 (GstRMDemuxStream, 1); |
| |
| stream->id = RMDEMUX_GUINT16_GET (data); |
| stream->index = NULL; |
| stream->seek_offset = 0; |
| stream->last_ts = -1; |
| stream->next_ts = -1; |
| stream->discont = TRUE; |
| stream->adapter = gst_adapter_new (); |
| GST_LOG_OBJECT (rmdemux, "stream_number=%d", stream->id); |
| |
| /* parse the bitrates */ |
| max_bitrate = RMDEMUX_GUINT32_GET (data + 2); |
| avg_bitrate = RMDEMUX_GUINT32_GET (data + 6); |
| stream->bitrate = avg_bitrate; |
| GST_LOG_OBJECT (rmdemux, "Stream max bitrate=%u", max_bitrate); |
| GST_LOG_OBJECT (rmdemux, "Stream avg bitrate=%u", avg_bitrate); |
| if (max_bitrate != 0) { |
| if (stream->pending_tags == NULL) |
| stream->pending_tags = gst_tag_list_new_empty (); |
| gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, |
| GST_TAG_MAXIMUM_BITRATE, max_bitrate, NULL); |
| } |
| if (avg_bitrate != 0) { |
| if (stream->pending_tags == NULL) |
| stream->pending_tags = gst_tag_list_new_empty (); |
| gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, |
| GST_TAG_BITRATE, avg_bitrate, NULL); |
| } |
| |
| offset = 30; |
| stream1_type_string = gst_rm_utils_read_string8 (data + offset, |
| length - offset, &str_len); |
| offset += str_len; |
| stream2_type_string = gst_rm_utils_read_string8 (data + offset, |
| length - offset, &str_len); |
| offset += str_len; |
| |
| /* stream1_type_string for audio and video stream is a "put_whatever_you_want" field : |
| * observed values : |
| * - "[The ]Video/Audio Stream" (File produced by an official Real encoder) |
| * - "RealVideoPremierePlugIn-VIDEO/AUDIO" (File produced by Abobe Premiere) |
| * |
| * so, we should not rely on it to know which stream type it is |
| */ |
| |
| GST_LOG_OBJECT (rmdemux, "stream type: %s", stream1_type_string); |
| GST_LOG_OBJECT (rmdemux, "MIME type=%s", stream2_type_string); |
| |
| if (strcmp (stream2_type_string, "video/x-pn-realvideo") == 0) { |
| stream_type = GST_RMDEMUX_STREAM_VIDEO; |
| } else if (strcmp (stream2_type_string, |
| "video/x-pn-multirate-realvideo") == 0) { |
| stream_type = GST_RMDEMUX_STREAM_VIDEO; |
| } else if (strcmp (stream2_type_string, "audio/x-pn-realaudio") == 0) { |
| stream_type = GST_RMDEMUX_STREAM_AUDIO; |
| } else if (strcmp (stream2_type_string, |
| "audio/x-pn-multirate-realaudio") == 0) { |
| stream_type = GST_RMDEMUX_STREAM_AUDIO; |
| } else if (strcmp (stream2_type_string, |
| "audio/x-pn-multirate-realaudio-live") == 0) { |
| stream_type = GST_RMDEMUX_STREAM_AUDIO; |
| } else if (strcmp (stream2_type_string, "audio/x-ralf-mpeg4-generic") == 0) { |
| /* Another audio type found in the real testsuite */ |
| stream_type = GST_RMDEMUX_STREAM_AUDIO; |
| } else if (strcmp (stream1_type_string, "") == 0 && |
| strcmp (stream2_type_string, "logical-fileinfo") == 0) { |
| stream_type = GST_RMDEMUX_STREAM_FILEINFO; |
| } else { |
| stream_type = GST_RMDEMUX_STREAM_UNKNOWN; |
| GST_WARNING_OBJECT (rmdemux, "unknown stream type \"%s\",\"%s\"", |
| stream1_type_string, stream2_type_string); |
| } |
| g_free (stream1_type_string); |
| g_free (stream2_type_string); |
| |
| offset += 4; |
| |
| stream->subtype = stream_type; |
| switch (stream_type) { |
| |
| case GST_RMDEMUX_STREAM_VIDEO: |
| /* RV10/RV20/RV30/RV40 => video/x-pn-realvideo, version=1,2,3,4 */ |
| stream->fourcc = RMDEMUX_FOURCC_GET (data + offset + 8); |
| stream->width = RMDEMUX_GUINT16_GET (data + offset + 12); |
| stream->height = RMDEMUX_GUINT16_GET (data + offset + 14); |
| stream->rate = RMDEMUX_GUINT16_GET (data + offset + 16); |
| stream->subformat = RMDEMUX_GUINT32_GET (data + offset + 26); |
| stream->format = RMDEMUX_GUINT32_GET (data + offset + 30); |
| stream->extra_data_size = length - (offset + 26); |
| stream->extra_data = (guint8 *) data + offset + 26; |
| /* Natural way to represent framerates here requires unsigned 32 bit |
| * numerator, which we don't have. For the nasty case, approximate... |
| */ |
| { |
| guint32 numerator = RMDEMUX_GUINT16_GET (data + offset + 22) * 65536 + |
| RMDEMUX_GUINT16_GET (data + offset + 24); |
| if (numerator > G_MAXINT) { |
| stream->framerate_numerator = (gint) (numerator >> 1); |
| stream->framerate_denominator = 32768; |
| } else { |
| stream->framerate_numerator = (gint) numerator; |
| stream->framerate_denominator = 65536; |
| } |
| } |
| |
| GST_DEBUG_OBJECT (rmdemux, |
| "Video stream with fourcc=%" GST_FOURCC_FORMAT |
| " width=%d height=%d rate=%d framerate=%d/%d subformat=%x format=%x extra_data_size=%d", |
| GST_FOURCC_ARGS (stream->fourcc), stream->width, stream->height, |
| stream->rate, stream->framerate_numerator, |
| stream->framerate_denominator, stream->subformat, stream->format, |
| stream->extra_data_size); |
| break; |
| case GST_RMDEMUX_STREAM_AUDIO:{ |
| stream->version = RMDEMUX_GUINT16_GET (data + offset + 4); |
| GST_INFO ("stream version = %u", stream->version); |
| switch (stream->version) { |
| case 3: |
| stream->fourcc = GST_RM_AUD_14_4; |
| stream->packet_size = 20; |
| stream->rate = 8000; |
| stream->n_channels = 1; |
| stream->sample_width = 16; |
| stream->flavor = 1; |
| stream->leaf_size = 0; |
| stream->height = 0; |
| break; |
| case 4: |
| stream->flavor = RMDEMUX_GUINT16_GET (data + offset + 22); |
| stream->packet_size = RMDEMUX_GUINT32_GET (data + offset + 24); |
| /* stream->frame_size = RMDEMUX_GUINT32_GET (data + offset + 42); */ |
| stream->leaf_size = RMDEMUX_GUINT16_GET (data + offset + 44); |
| stream->height = RMDEMUX_GUINT16_GET (data + offset + 40); |
| stream->rate = RMDEMUX_GUINT16_GET (data + offset + 48); |
| stream->sample_width = RMDEMUX_GUINT16_GET (data + offset + 52); |
| stream->n_channels = RMDEMUX_GUINT16_GET (data + offset + 54); |
| stream->fourcc = RMDEMUX_FOURCC_GET (data + offset + 62); |
| stream->extra_data_size = RMDEMUX_GUINT32_GET (data + offset + 69); |
| GST_DEBUG_OBJECT (rmdemux, "%u bytes of extra codec data", |
| stream->extra_data_size); |
| if (length - (offset + 73) >= stream->extra_data_size) { |
| stream->extra_data = (guint8 *) data + offset + 73; |
| } else { |
| GST_WARNING_OBJECT (rmdemux, "codec data runs beyond MDPR chunk"); |
| stream->extra_data_size = 0; |
| } |
| break; |
| case 5: |
| stream->flavor = RMDEMUX_GUINT16_GET (data + offset + 22); |
| stream->packet_size = RMDEMUX_GUINT32_GET (data + offset + 24); |
| /* stream->frame_size = RMDEMUX_GUINT32_GET (data + offset + 42); */ |
| stream->leaf_size = RMDEMUX_GUINT16_GET (data + offset + 44); |
| stream->height = RMDEMUX_GUINT16_GET (data + offset + 40); |
| stream->rate = RMDEMUX_GUINT16_GET (data + offset + 54); |
| stream->sample_width = RMDEMUX_GUINT16_GET (data + offset + 58); |
| stream->n_channels = RMDEMUX_GUINT16_GET (data + offset + 60); |
| stream->fourcc = RMDEMUX_FOURCC_GET (data + offset + 66); |
| stream->extra_data_size = RMDEMUX_GUINT32_GET (data + offset + 74); |
| GST_DEBUG_OBJECT (rmdemux, "%u bytes of extra codec data", |
| stream->extra_data_size); |
| if (length - (offset + 78) >= stream->extra_data_size) { |
| stream->extra_data = (guint8 *) data + offset + 78; |
| } else { |
| GST_WARNING_OBJECT (rmdemux, "codec data runs beyond MDPR chunk"); |
| stream->extra_data_size = 0; |
| } |
| break; |
| default:{ |
| GST_WARNING_OBJECT (rmdemux, "Unhandled audio stream version %d", |
| stream->version); |
| break; |
| } |
| } |
| /* 14_4, 28_8, cook, dnet, sipr, raac, racp, ralf, atrc */ |
| GST_DEBUG_OBJECT (rmdemux, |
| "Audio stream with rate=%d sample_width=%d n_channels=%d", |
| stream->rate, stream->sample_width, stream->n_channels); |
| |
| break; |
| } |
| case GST_RMDEMUX_STREAM_FILEINFO: |
| { |
| int element_nb; |
| |
| /* Length of this section */ |
| GST_DEBUG_OBJECT (rmdemux, "length2: 0x%08x", |
| RMDEMUX_GUINT32_GET (data + offset)); |
| offset += 4; |
| |
| /* Unknown : 00 00 00 00 */ |
| offset += 4; |
| |
| /* Number of variables that would follow (loop iterations) */ |
| element_nb = RMDEMUX_GUINT32_GET (data + offset); |
| offset += 4; |
| |
| while (element_nb) { |
| /* Category Id : 00 00 00 XX 00 00 */ |
| offset += 6; |
| |
| /* Variable Name */ |
| offset += re_skip_pascal_string (data + offset); |
| |
| /* Variable Value Type */ |
| /* 00 00 00 00 00 => integer/boolean, preceded by length */ |
| /* 00 00 00 02 00 => pascal string, preceded by length, no trailing \0 */ |
| offset += 5; |
| |
| /* Variable Value */ |
| offset += re_skip_pascal_string (data + offset); |
| |
| element_nb--; |
| } |
| } |
| break; |
| case GST_RMDEMUX_STREAM_UNKNOWN: |
| default: |
| break; |
| } |
| |
| gst_rmdemux_add_stream (rmdemux, stream); |
| } |
| |
| static guint |
| gst_rmdemux_parse_indx (GstRMDemux * rmdemux, const guint8 * data, int length) |
| { |
| int n; |
| int id; |
| |
| n = RMDEMUX_GUINT32_GET (data); |
| id = RMDEMUX_GUINT16_GET (data + 4); |
| rmdemux->index_offset = RMDEMUX_GUINT32_GET (data + 6); |
| |
| GST_DEBUG_OBJECT (rmdemux, "Number of indices=%d Stream ID=%d length=%d", n, |
| id, length); |
| |
| /* Point to the next index_stream */ |
| rmdemux->index_stream = gst_rmdemux_get_stream_by_id (rmdemux, id); |
| |
| /* Return the length of the index */ |
| return 14 * n; |
| } |
| |
| static void |
| gst_rmdemux_parse_indx_data (GstRMDemux * rmdemux, const guint8 * data, |
| int length) |
| { |
| int i; |
| int n; |
| GstRMDemuxIndex *index; |
| |
| /* The number of index records */ |
| n = length / 14; |
| |
| if (rmdemux->index_stream == NULL) |
| return; |
| |
| /* don't parse the index a second time when operating pull-based and |
| * reaching the end of the file */ |
| if (rmdemux->index_stream->index_length > 0) { |
| GST_DEBUG_OBJECT (rmdemux, "Already have an index for this stream"); |
| return; |
| } |
| |
| index = g_malloc (sizeof (GstRMDemuxIndex) * n); |
| rmdemux->index_stream->index = index; |
| rmdemux->index_stream->index_length = n; |
| |
| for (i = 0; i < n; i++) { |
| index[i].timestamp = RMDEMUX_GUINT32_GET (data + 2) * GST_MSECOND; |
| index[i].offset = RMDEMUX_GUINT32_GET (data + 6); |
| |
| GST_DEBUG_OBJECT (rmdemux, "Index found for timestamp=%f (at offset=%x)", |
| gst_guint64_to_gdouble (index[i].timestamp) / GST_SECOND, |
| index[i].offset); |
| data += 14; |
| } |
| } |
| |
| static void |
| gst_rmdemux_parse_data (GstRMDemux * rmdemux, const guint8 * data, int length) |
| { |
| rmdemux->n_chunks = RMDEMUX_GUINT32_GET (data); |
| rmdemux->data_offset = RMDEMUX_GUINT32_GET (data + 4); |
| rmdemux->chunk_index = 0; |
| GST_DEBUG_OBJECT (rmdemux, "Data chunk found with %d packets " |
| "(next data at 0x%08x)", rmdemux->n_chunks, rmdemux->data_offset); |
| } |
| |
| static void |
| gst_rmdemux_parse_cont (GstRMDemux * rmdemux, const guint8 * data, int length) |
| { |
| GstTagList *tags; |
| |
| tags = gst_rm_utils_read_tags (data, length, gst_rm_utils_read_string16); |
| |
| if (tags) { |
| GstTagList *old_tags = rmdemux->pending_tags; |
| |
| GST_LOG_OBJECT (rmdemux, "tags: %" GST_PTR_FORMAT, tags); |
| |
| rmdemux->pending_tags = |
| gst_tag_list_merge (old_tags, tags, GST_TAG_MERGE_APPEND); |
| |
| gst_tag_list_unref (tags); |
| if (old_tags) |
| gst_tag_list_unref (old_tags); |
| |
| gst_tag_list_set_scope (rmdemux->pending_tags, GST_TAG_SCOPE_GLOBAL); |
| } |
| } |
| |
| static void |
| gst_rmdemux_stream_clear_cached_subpackets (GstRMDemux * rmdemux, |
| GstRMDemuxStream * stream) |
| { |
| if (stream->subpackets == NULL || stream->subpackets->len == 0) |
| return; |
| |
| GST_DEBUG_OBJECT (rmdemux, "discarding %u previously collected subpackets", |
| stream->subpackets->len); |
| g_ptr_array_foreach (stream->subpackets, (GFunc) gst_mini_object_unref, NULL); |
| g_ptr_array_set_size (stream->subpackets, 0); |
| } |
| |
| static GstFlowReturn |
| gst_rmdemux_descramble_audio (GstRMDemux * rmdemux, GstRMDemuxStream * stream) |
| { |
| GstFlowReturn ret = GST_FLOW_ERROR; |
| GstBuffer *outbuf; |
| GstMapInfo outmap; |
| guint packet_size = stream->packet_size; |
| guint height = stream->subpackets->len; |
| guint leaf_size = stream->leaf_size; |
| guint p, x; |
| |
| g_assert (stream->height == height); |
| |
| GST_LOG ("packet_size = %u, leaf_size = %u, height= %u", packet_size, |
| leaf_size, height); |
| |
| outbuf = gst_buffer_new_and_alloc (height * packet_size); |
| gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE); |
| |
| for (p = 0; p < height; ++p) { |
| GstBuffer *b = g_ptr_array_index (stream->subpackets, p); |
| GstMapInfo map; |
| |
| gst_buffer_map (b, &map, GST_MAP_READ); |
| |
| if (p == 0) { |
| GST_BUFFER_PTS (outbuf) = GST_BUFFER_PTS (b); |
| GST_BUFFER_DTS (outbuf) = GST_BUFFER_DTS (b); |
| } |
| |
| for (x = 0; x < packet_size / leaf_size; ++x) { |
| guint idx; |
| |
| idx = height * x + ((height + 1) / 2) * (p % 2) + (p / 2); |
| |
| /* GST_LOG ("%3u => %3u", (height * p) + x, idx); */ |
| memcpy (outmap.data + leaf_size * idx, map.data + leaf_size * x, |
| leaf_size); |
| } |
| gst_buffer_unmap (b, &map); |
| } |
| gst_buffer_unmap (outbuf, &outmap); |
| |
| /* some decoders, such as realaudiodec, need to be fed in packet units */ |
| for (p = 0; p < height; ++p) { |
| GstBuffer *subbuf; |
| |
| subbuf = |
| gst_buffer_copy_region (outbuf, GST_BUFFER_COPY_ALL, p * packet_size, |
| packet_size); |
| |
| GST_LOG_OBJECT (rmdemux, "pushing buffer dts %" GST_TIME_FORMAT ", pts %" |
| GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_DTS (subbuf)), |
| GST_TIME_ARGS (GST_BUFFER_PTS (subbuf))); |
| |
| if (stream->discont) { |
| GST_BUFFER_FLAG_SET (subbuf, GST_BUFFER_FLAG_DISCONT); |
| stream->discont = FALSE; |
| } |
| |
| ret = gst_pad_push (stream->pad, subbuf); |
| if (ret != GST_FLOW_OK) |
| break; |
| } |
| |
| gst_buffer_unref (outbuf); |
| |
| gst_rmdemux_stream_clear_cached_subpackets (rmdemux, stream); |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_rmdemux_descramble_dnet_audio (GstRMDemux * rmdemux, |
| GstRMDemuxStream * stream) |
| { |
| GstBuffer *buf; |
| |
| buf = g_ptr_array_index (stream->subpackets, 0); |
| g_ptr_array_index (stream->subpackets, 0) = NULL; |
| g_ptr_array_set_size (stream->subpackets, 0); |
| |
| buf = gst_rm_utils_descramble_dnet_buffer (buf); |
| |
| if (stream->discont) { |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); |
| stream->discont = FALSE; |
| } |
| return gst_pad_push (stream->pad, buf); |
| } |
| |
| static GstFlowReturn |
| gst_rmdemux_descramble_mp4a_audio (GstRMDemux * rmdemux, |
| GstRMDemuxStream * stream) |
| { |
| GstFlowReturn res; |
| GstBuffer *buf, *outbuf; |
| guint frames, index, i; |
| GstMapInfo map; |
| GstClockTime timestamp; |
| |
| res = GST_FLOW_OK; |
| |
| buf = g_ptr_array_index (stream->subpackets, 0); |
| g_ptr_array_index (stream->subpackets, 0) = NULL; |
| g_ptr_array_set_size (stream->subpackets, 0); |
| |
| gst_buffer_map (buf, &map, GST_MAP_READ); |
| timestamp = GST_BUFFER_PTS (buf); |
| |
| frames = (map.data[1] & 0xf0) >> 4; |
| index = 2 * frames + 2; |
| |
| for (i = 0; i < frames; i++) { |
| guint len = (map.data[i * 2 + 2] << 8) | map.data[i * 2 + 3]; |
| |
| outbuf = gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL, index, len); |
| if (i == 0) { |
| GST_BUFFER_PTS (outbuf) = timestamp; |
| GST_BUFFER_DTS (outbuf) = timestamp; |
| } |
| |
| index += len; |
| |
| if (stream->discont) { |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); |
| stream->discont = FALSE; |
| } |
| res = gst_pad_push (stream->pad, outbuf); |
| if (res != GST_FLOW_OK) |
| break; |
| } |
| gst_buffer_unmap (buf, &map); |
| gst_buffer_unref (buf); |
| return res; |
| } |
| |
| static GstFlowReturn |
| gst_rmdemux_descramble_sipr_audio (GstRMDemux * rmdemux, |
| GstRMDemuxStream * stream) |
| { |
| GstFlowReturn ret; |
| GstBuffer *outbuf; |
| GstMapInfo outmap; |
| guint packet_size = stream->packet_size; |
| guint height = stream->subpackets->len; |
| guint p; |
| |
| g_assert (stream->height == height); |
| |
| GST_LOG ("packet_size = %u, leaf_size = %u, height= %u", packet_size, |
| stream->leaf_size, height); |
| |
| outbuf = gst_buffer_new_and_alloc (height * packet_size); |
| gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE); |
| |
| for (p = 0; p < height; ++p) { |
| GstBuffer *b = g_ptr_array_index (stream->subpackets, p); |
| |
| if (p == 0) { |
| GST_BUFFER_DTS (outbuf) = GST_BUFFER_DTS (b); |
| GST_BUFFER_PTS (outbuf) = GST_BUFFER_PTS (b); |
| } |
| |
| gst_buffer_extract (b, 0, outmap.data + packet_size * p, packet_size); |
| } |
| gst_buffer_unmap (outbuf, &outmap); |
| |
| GST_LOG_OBJECT (rmdemux, "pushing buffer dts %" GST_TIME_FORMAT ", pts %" |
| GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_DTS (outbuf)), |
| GST_TIME_ARGS (GST_BUFFER_PTS (outbuf))); |
| |
| if (stream->discont) { |
| GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); |
| stream->discont = FALSE; |
| } |
| |
| outbuf = gst_rm_utils_descramble_sipr_buffer (outbuf); |
| |
| ret = gst_pad_push (stream->pad, outbuf); |
| |
| gst_rmdemux_stream_clear_cached_subpackets (rmdemux, stream); |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_rmdemux_handle_scrambled_packet (GstRMDemux * rmdemux, |
| GstRMDemuxStream * stream, GstBuffer * buf, gboolean keyframe) |
| { |
| GstFlowReturn ret; |
| |
| if (stream->subpackets == NULL) |
| stream->subpackets = g_ptr_array_sized_new (stream->subpackets_needed); |
| |
| GST_LOG ("Got subpacket %u/%u, len=%" G_GSIZE_FORMAT ", key=%d", |
| stream->subpackets->len + 1, stream->subpackets_needed, |
| gst_buffer_get_size (buf), keyframe); |
| |
| if (keyframe && stream->subpackets->len > 0) { |
| gst_rmdemux_stream_clear_cached_subpackets (rmdemux, stream); |
| } |
| |
| g_ptr_array_add (stream->subpackets, buf); |
| |
| if (stream->subpackets->len < stream->subpackets_needed) |
| return GST_FLOW_OK; |
| |
| g_assert (stream->subpackets->len >= 1); |
| |
| switch (stream->fourcc) { |
| case GST_RM_AUD_DNET: |
| ret = gst_rmdemux_descramble_dnet_audio (rmdemux, stream); |
| break; |
| case GST_RM_AUD_COOK: |
| case GST_RM_AUD_ATRC: |
| ret = gst_rmdemux_descramble_audio (rmdemux, stream); |
| break; |
| case GST_RM_AUD_RAAC: |
| case GST_RM_AUD_RACP: |
| ret = gst_rmdemux_descramble_mp4a_audio (rmdemux, stream); |
| break; |
| case GST_RM_AUD_SIPR: |
| ret = gst_rmdemux_descramble_sipr_audio (rmdemux, stream); |
| break; |
| default: |
| ret = GST_FLOW_ERROR; |
| g_assert_not_reached (); |
| } |
| |
| return ret; |
| } |
| |
| #define PARSE_NUMBER(data, size, number, label) \ |
| G_STMT_START { \ |
| if (size < 2) \ |
| goto label; \ |
| number = GST_READ_UINT16_BE (data); \ |
| if (!(number & 0xc000)) { \ |
| if (size < 4) \ |
| goto label; \ |
| number = GST_READ_UINT32_BE (data); \ |
| data += 4; \ |
| size -= 4; \ |
| } else { \ |
| number &= 0x3fff; \ |
| data += 2; \ |
| size -= 2; \ |
| } \ |
| } G_STMT_END |
| |
| static GstFlowReturn |
| gst_rmdemux_parse_video_packet (GstRMDemux * rmdemux, GstRMDemuxStream * stream, |
| GstBuffer * in, guint offset, guint16 version, |
| GstClockTime timestamp, gboolean key) |
| { |
| GstFlowReturn ret; |
| GstMapInfo map; |
| const guint8 *data; |
| gsize size; |
| |
| gst_buffer_map (in, &map, GST_MAP_READ); |
| |
| data = map.data + offset; |
| size = map.size - offset; |
| |
| /* if size <= 2, we want this method to return the same GstFlowReturn as it |
| * was previously for that given stream. */ |
| ret = GST_PAD_LAST_FLOW_RETURN (stream->pad); |
| |
| while (size > 2) { |
| guint8 pkg_header; |
| guint pkg_offset; |
| guint pkg_length; |
| guint pkg_subseq = 0, pkg_seqnum = G_MAXUINT; |
| guint fragment_size; |
| GstBuffer *fragment; |
| |
| pkg_header = *data++; |
| size--; |
| |
| /* packet header |
| * bit 7: 1=last block in block chain |
| * bit 6: 1=short header (only one block?) |
| */ |
| if ((pkg_header & 0xc0) == 0x40) { |
| /* skip unknown byte */ |
| data++; |
| size--; |
| pkg_offset = 0; |
| pkg_length = size; |
| } else { |
| if ((pkg_header & 0x40) == 0) { |
| pkg_subseq = (*data++) & 0x7f; |
| size--; |
| } else { |
| pkg_subseq = 0; |
| } |
| |
| /* length */ |
| PARSE_NUMBER (data, size, pkg_length, not_enough_data); |
| |
| /* offset */ |
| PARSE_NUMBER (data, size, pkg_offset, not_enough_data); |
| |
| /* seqnum */ |
| if (size < 1) |
| goto not_enough_data; |
| |
| pkg_seqnum = *data++; |
| size--; |
| } |
| |
| GST_DEBUG_OBJECT (rmdemux, |
| "seq %d, subseq %d, offset %d, length %d, size %" G_GSIZE_FORMAT |
| ", header %02x", pkg_seqnum, pkg_subseq, pkg_offset, pkg_length, size, |
| pkg_header); |
| |
| /* calc size of fragment */ |
| if ((pkg_header & 0xc0) == 0x80) { |
| fragment_size = pkg_offset; |
| } else { |
| if ((pkg_header & 0xc0) == 0) |
| fragment_size = size; |
| else |
| fragment_size = pkg_length; |
| } |
| GST_DEBUG_OBJECT (rmdemux, "fragment size %d", fragment_size); |
| |
| /* get the fragment */ |
| fragment = |
| gst_buffer_copy_region (in, GST_BUFFER_COPY_ALL, data - map.data, |
| fragment_size); |
| |
| if (pkg_subseq == 1) { |
| GST_DEBUG_OBJECT (rmdemux, "start new fragment"); |
| gst_adapter_clear (stream->adapter); |
| stream->frag_current = 0; |
| stream->frag_count = 0; |
| stream->frag_length = pkg_length; |
| } else if (pkg_subseq == 0) { |
| GST_DEBUG_OBJECT (rmdemux, "non fragmented packet"); |
| stream->frag_current = 0; |
| stream->frag_count = 0; |
| stream->frag_length = fragment_size; |
| } |
| |
| /* put fragment in adapter */ |
| gst_adapter_push (stream->adapter, fragment); |
| stream->frag_offset[stream->frag_count] = stream->frag_current; |
| stream->frag_current += fragment_size; |
| stream->frag_count++; |
| |
| if (stream->frag_count > MAX_FRAGS) |
| goto too_many_fragments; |
| |
| GST_DEBUG_OBJECT (rmdemux, "stored fragment in adapter %d/%d", |
| stream->frag_current, stream->frag_length); |
| |
| /* flush fragment when complete */ |
| if (stream->frag_current >= stream->frag_length) { |
| GstBuffer *out; |
| GstMapInfo outmap; |
| guint8 *outdata; |
| guint header_size; |
| gint i, avail; |
| |
| /* calculate header size, which is: |
| * 1 byte for the number of fragments - 1 |
| * for each fragment: |
| * 4 bytes 0x00000001 little endian |
| * 4 bytes fragment offset |
| * |
| * This is also the matroska header for realvideo, the decoder needs the |
| * fragment offsets, both in ffmpeg and real .so, so we just give it that |
| * in front of the data. |
| */ |
| header_size = 1 + (8 * (stream->frag_count)); |
| |
| GST_DEBUG_OBJECT (rmdemux, |
| "fragmented completed. count %d, header_size %u", stream->frag_count, |
| header_size); |
| |
| avail = gst_adapter_available (stream->adapter); |
| |
| out = gst_buffer_new_and_alloc (header_size + avail); |
| gst_buffer_map (out, &outmap, GST_MAP_WRITE); |
| outdata = outmap.data; |
| |
| /* create header */ |
| *outdata++ = stream->frag_count - 1; |
| for (i = 0; i < stream->frag_count; i++) { |
| GST_WRITE_UINT32_LE (outdata, 0x00000001); |
| outdata += 4; |
| GST_WRITE_UINT32_LE (outdata, stream->frag_offset[i]); |
| outdata += 4; |
| } |
| |
| /* copy packet data after the header now */ |
| gst_adapter_copy (stream->adapter, outdata, 0, avail); |
| gst_adapter_flush (stream->adapter, avail); |
| |
| stream->frag_current = 0; |
| stream->frag_count = 0; |
| stream->frag_length = 0; |
| |
| if (timestamp != -1) { |
| if (rmdemux->first_ts != -1 && timestamp > rmdemux->first_ts) |
| timestamp -= rmdemux->first_ts; |
| else |
| timestamp = 0; |
| |
| if (rmdemux->base_ts != -1) |
| timestamp += rmdemux->base_ts; |
| } |
| gst_buffer_unmap (out, &outmap); |
| |
| /* video has DTS */ |
| GST_BUFFER_DTS (out) = timestamp; |
| GST_BUFFER_PTS (out) = GST_CLOCK_TIME_NONE; |
| |
| GST_LOG_OBJECT (rmdemux, "pushing timestamp %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (timestamp)); |
| |
| if (stream->discont) { |
| GST_BUFFER_FLAG_SET (out, GST_BUFFER_FLAG_DISCONT); |
| stream->discont = FALSE; |
| } |
| |
| if (!key) { |
| GST_BUFFER_FLAG_SET (out, GST_BUFFER_FLAG_DELTA_UNIT); |
| } |
| |
| ret = gst_pad_push (stream->pad, out); |
| ret = gst_flow_combiner_update_flow (rmdemux->flowcombiner, ret); |
| if (ret != GST_FLOW_OK) |
| break; |
| |
| timestamp = GST_CLOCK_TIME_NONE; |
| } |
| data += fragment_size; |
| size -= fragment_size; |
| } |
| GST_DEBUG_OBJECT (rmdemux, "%" G_GSIZE_FORMAT " bytes left", size); |
| |
| done: |
| gst_buffer_unmap (in, &map); |
| gst_buffer_unref (in); |
| |
| return ret; |
| |
| /* ERRORS */ |
| not_enough_data: |
| { |
| GST_ELEMENT_WARNING (rmdemux, STREAM, DECODE, ("Skipping bad packet."), |
| (NULL)); |
| ret = GST_FLOW_OK; |
| goto done; |
| } |
| too_many_fragments: |
| { |
| GST_ELEMENT_ERROR (rmdemux, STREAM, DECODE, |
| ("Got more fragments (%u) than can be handled (%u)", |
| stream->frag_count, MAX_FRAGS), (NULL)); |
| ret = GST_FLOW_ERROR; |
| goto done; |
| } |
| } |
| |
| static GstFlowReturn |
| gst_rmdemux_parse_audio_packet (GstRMDemux * rmdemux, GstRMDemuxStream * stream, |
| GstBuffer * in, guint offset, guint16 version, |
| GstClockTime timestamp, gboolean key) |
| { |
| GstFlowReturn ret; |
| GstBuffer *buffer; |
| |
| buffer = gst_buffer_copy_region (in, GST_BUFFER_COPY_MEMORY, offset, -1); |
| |
| if (rmdemux->first_ts != -1 && timestamp > rmdemux->first_ts) |
| timestamp -= rmdemux->first_ts; |
| else |
| timestamp = 0; |
| |
| if (rmdemux->base_ts != -1) |
| timestamp += rmdemux->base_ts; |
| |
| GST_BUFFER_PTS (buffer) = timestamp; |
| GST_BUFFER_DTS (buffer) = timestamp; |
| |
| if (stream->needs_descrambling) { |
| GST_LOG_OBJECT (rmdemux, "descramble timestamp %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (timestamp)); |
| ret = gst_rmdemux_handle_scrambled_packet (rmdemux, stream, buffer, key); |
| } else { |
| GST_LOG_OBJECT (rmdemux, |
| "Pushing buffer of size %" G_GSIZE_FORMAT ", timestamp %" |
| GST_TIME_FORMAT "to pad %s", gst_buffer_get_size (buffer), |
| GST_TIME_ARGS (timestamp), GST_PAD_NAME (stream->pad)); |
| |
| if (stream->discont) { |
| GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); |
| stream->discont = FALSE; |
| } |
| ret = gst_pad_push (stream->pad, buffer); |
| } |
| |
| gst_buffer_unref (in); |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_rmdemux_parse_packet (GstRMDemux * rmdemux, GstBuffer * in, guint16 version) |
| { |
| guint16 id; |
| GstRMDemuxStream *stream; |
| gsize size, offset; |
| GstFlowReturn cret, ret; |
| GstClockTime timestamp; |
| gboolean key; |
| GstMapInfo map; |
| guint8 *data; |
| guint8 flags; |
| guint32 ts; |
| |
| gst_buffer_map (in, &map, GST_MAP_READ); |
| data = map.data; |
| size = map.size; |
| |
| /* stream number */ |
| id = RMDEMUX_GUINT16_GET (data); |
| |
| stream = gst_rmdemux_get_stream_by_id (rmdemux, id); |
| if (!stream || !stream->pad) |
| goto unknown_stream; |
| |
| /* timestamp in Msec */ |
| ts = RMDEMUX_GUINT32_GET (data + 2); |
| timestamp = ts * GST_MSECOND; |
| |
| rmdemux->segment.position = timestamp; |
| |
| GST_LOG_OBJECT (rmdemux, "Parsing a packet for stream=%d, timestamp=%" |
| GST_TIME_FORMAT ", size %" G_GSIZE_FORMAT ", version=%d, ts=%u", id, |
| GST_TIME_ARGS (timestamp), size, version, ts); |
| |
| if (rmdemux->first_ts == GST_CLOCK_TIME_NONE) { |
| GST_DEBUG_OBJECT (rmdemux, "First timestamp: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (timestamp)); |
| rmdemux->first_ts = timestamp; |
| } |
| |
| /* skip stream_id and timestamp */ |
| data += (2 + 4); |
| size -= (2 + 4); |
| |
| /* get flags */ |
| flags = GST_READ_UINT8 (data + 1); |
| |
| data += 2; |
| size -= 2; |
| |
| /* version 1 has an extra byte */ |
| if (version == 1) { |
| data += 1; |
| size -= 1; |
| } |
| offset = data - map.data; |
| gst_buffer_unmap (in, &map); |
| |
| key = (flags & 0x02) != 0; |
| GST_DEBUG_OBJECT (rmdemux, "flags %d, Keyframe %d", flags, key); |
| |
| if (rmdemux->need_newsegment) { |
| GstEvent *event; |
| |
| event = gst_event_new_segment (&rmdemux->segment); |
| |
| GST_DEBUG_OBJECT (rmdemux, "sending NEWSEGMENT event, segment.start= %" |
| GST_TIME_FORMAT, GST_TIME_ARGS (rmdemux->segment.start)); |
| |
| gst_rmdemux_send_event (rmdemux, event); |
| rmdemux->need_newsegment = FALSE; |
| |
| if (rmdemux->pending_tags != NULL) { |
| gst_rmdemux_send_event (rmdemux, |
| gst_event_new_tag (rmdemux->pending_tags)); |
| rmdemux->pending_tags = NULL; |
| } |
| } |
| |
| if (stream->pending_tags != NULL) { |
| GST_LOG_OBJECT (stream->pad, "tags %" GST_PTR_FORMAT, stream->pending_tags); |
| gst_pad_push_event (stream->pad, gst_event_new_tag (stream->pending_tags)); |
| stream->pending_tags = NULL; |
| } |
| |
| if ((rmdemux->offset + size) <= stream->seek_offset) { |
| GST_DEBUG_OBJECT (rmdemux, |
| "Stream %d is skipping: seek_offset=%d, offset=%d, size=%" |
| G_GSIZE_FORMAT, stream->id, stream->seek_offset, rmdemux->offset, size); |
| cret = GST_FLOW_OK; |
| gst_buffer_unref (in); |
| goto beach; |
| } |
| |
| /* do special headers */ |
| if (stream->subtype == GST_RMDEMUX_STREAM_VIDEO) { |
| ret = |
| gst_rmdemux_parse_video_packet (rmdemux, stream, in, offset, |
| version, timestamp, key); |
| } else if (stream->subtype == GST_RMDEMUX_STREAM_AUDIO) { |
| ret = |
| gst_rmdemux_parse_audio_packet (rmdemux, stream, in, offset, |
| version, timestamp, key); |
| } else { |
| gst_buffer_unref (in); |
| ret = GST_FLOW_OK; |
| } |
| |
| cret = gst_flow_combiner_update_pad_flow (rmdemux->flowcombiner, stream->pad, |
| ret); |
| |
| beach: |
| return cret; |
| |
| /* ERRORS */ |
| unknown_stream: |
| { |
| GST_WARNING_OBJECT (rmdemux, "No stream for stream id %d in parsing " |
| "data packet", id); |
| gst_buffer_unmap (in, &map); |
| gst_buffer_unref (in); |
| return GST_FLOW_OK; |
| } |
| } |
| |
| gboolean |
| gst_rmdemux_plugin_init (GstPlugin * plugin) |
| { |
| return gst_element_register (plugin, "rmdemux", |
| GST_RANK_PRIMARY, GST_TYPE_RMDEMUX); |
| } |