mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 12:11:13 +00:00
c3b5d7e914
Video uses DTS as timestamps. Set the DTS on the buffers and leave the PTS undefined to make the decoder do the right thing. We can then remove the code that tries to guess the PTS from the DTS.
2571 lines
77 KiB
C
2571 lines
77 KiB
C
/* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, 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;
|
|
GstFlowReturn last_flow;
|
|
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_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_rmdemux_sink_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_rmdemux_videosrc_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&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;
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
default:
|
|
res = gst_pad_query_default (pad, parent, query);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
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;
|
|
|
|
g_object_unref (stream->adapter);
|
|
gst_rmdemux_stream_clear_cached_subpackets (rmdemux, stream);
|
|
gst_element_remove_pad (GST_ELEMENT (rmdemux), stream->pad);
|
|
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);
|
|
}
|
|
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;
|
|
}
|
|
|
|
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;
|
|
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_ERROR (rmdemux, STREAM, FAILED,
|
|
(NULL), ("stream stopped, reason %s", reason));
|
|
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;
|
|
stream->last_flow = GST_FLOW_OK;
|
|
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_free (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);
|
|
g_free (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) {
|
|
|
|
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), "%u", stream->id);
|
|
gst_pad_push_event (stream->pad, gst_event_new_stream_start (stream_id));
|
|
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);
|
|
}
|
|
|
|
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->last_flow = GST_FLOW_OK;
|
|
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);
|
|
|
|
GST_LOG_OBJECT (rmdemux, "tags: %" GST_PTR_FORMAT, tags);
|
|
|
|
rmdemux->pending_tags =
|
|
gst_tag_list_merge (rmdemux->pending_tags, tags, GST_TAG_MERGE_APPEND);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_rmdemux_combine_flows (GstRMDemux * rmdemux, GstRMDemuxStream * stream,
|
|
GstFlowReturn ret)
|
|
{
|
|
GSList *cur;
|
|
|
|
/* store the value */
|
|
stream->last_flow = ret;
|
|
|
|
/* if it's success we can return the value right away */
|
|
if (ret == GST_FLOW_OK)
|
|
goto done;
|
|
|
|
/* any other error that is not-linked can be returned right
|
|
* away */
|
|
if (ret != GST_FLOW_NOT_LINKED)
|
|
goto done;
|
|
|
|
for (cur = rmdemux->streams; cur; cur = cur->next) {
|
|
GstRMDemuxStream *ostream = cur->data;
|
|
|
|
ret = ostream->last_flow;
|
|
/* some other return value (must be SUCCESS but we can return
|
|
* other values as well) */
|
|
if (ret != GST_FLOW_NOT_LINKED)
|
|
goto done;
|
|
}
|
|
/* if we get here, all other pads were unlinked and we return
|
|
* NOT_LINKED then */
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
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 = stream->last_flow;
|
|
|
|
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_rmdemux_combine_flows (rmdemux, stream, 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_rmdemux_combine_flows (rmdemux, stream, 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);
|
|
}
|