mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 17:50:36 +00:00
2672 lines
80 KiB
C
2672 lines
80 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., 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;
|
|
}
|
|
|
|
GST_ELEMENT_REGISTER_DEFINE (rmdemux, "rmdemux",
|
|
GST_RANK_PRIMARY, GST_TYPE_RMDEMUX);
|
|
|
|
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 ();
|
|
rmdemux->seek_seqnum = GST_SEQNUM_INVALID;
|
|
|
|
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;
|
|
guint32 seqnum;
|
|
|
|
GST_LOG_OBJECT (rmdemux, "Event on src: SEEK");
|
|
|
|
seqnum = gst_event_get_seqnum (event);
|
|
if (seqnum == rmdemux->seek_seqnum) {
|
|
GST_LOG_OBJECT (pad,
|
|
"Drop duplicated SEEK event seqnum %" G_GUINT32_FORMAT, seqnum);
|
|
gst_event_unref (event);
|
|
break;
|
|
}
|
|
|
|
/* 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;
|
|
guint32 seqnum = GST_SEQNUM_INVALID;
|
|
GstEvent *fl_event;
|
|
|
|
if (event) {
|
|
GST_DEBUG_OBJECT (rmdemux, "seek with event");
|
|
|
|
seqnum = gst_event_get_seqnum (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) {
|
|
fl_event = gst_event_new_flush_start ();
|
|
if (seqnum != GST_SEQNUM_INVALID)
|
|
gst_event_set_seqnum (fl_event, seqnum);
|
|
gst_pad_push_event (rmdemux->sinkpad, fl_event);
|
|
|
|
fl_event = gst_event_new_flush_start ();
|
|
if (seqnum != GST_SEQNUM_INVALID)
|
|
gst_event_set_seqnum (fl_event, seqnum);
|
|
gst_rmdemux_send_event (rmdemux, fl_event);
|
|
} 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) {
|
|
if (!gst_segment_do_seek (&rmdemux->segment, rate, format, flags,
|
|
cur_type, cur, stop_type, stop, &update)) {
|
|
ret = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
rmdemux->seek_seqnum = seqnum;
|
|
}
|
|
|
|
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. */
|
|
fl_event = gst_event_new_flush_stop (TRUE);
|
|
if (seqnum != GST_SEQNUM_INVALID)
|
|
gst_event_set_seqnum (fl_event, seqnum);
|
|
gst_pad_push_event (rmdemux->sinkpad, fl_event);
|
|
|
|
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) {
|
|
fl_event = gst_event_new_flush_stop (TRUE);
|
|
if (seqnum != GST_SEQNUM_INVALID)
|
|
gst_event_set_seqnum (fl_event, seqnum);
|
|
gst_rmdemux_send_event (rmdemux, fl_event);
|
|
}
|
|
|
|
/* 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) {
|
|
GstMessage *msg =
|
|
gst_message_new_segment_start (GST_OBJECT_CAST (rmdemux),
|
|
GST_FORMAT_TIME, rmdemux->segment.position);
|
|
if (seqnum != GST_SEQNUM_INVALID)
|
|
gst_message_set_seqnum (msg, seqnum);
|
|
gst_element_post_message (GST_ELEMENT_CAST (rmdemux), msg);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
rmdemux->seek_seqnum = GST_SEQNUM_INVALID;
|
|
}
|
|
|
|
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);
|
|
|
|
if (map.size < offset)
|
|
goto not_enough_data;
|
|
|
|
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);
|
|
|
|
if (map.size < (data - map.data) + fragment_size)
|
|
goto not_enough_data;
|
|
|
|
/* 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;
|
|
|
|
if (gst_buffer_get_size (in) < offset)
|
|
goto not_enough_data;
|
|
|
|
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);
|
|
}
|
|
|
|
done:
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (size < 4 + 6 + 1 + 2)
|
|
goto not_enough_data;
|
|
|
|
/* 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) {
|
|
if (size < 1)
|
|
goto not_enough_data;
|
|
|
|
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;
|
|
}
|
|
|
|
/* ERRORS */
|
|
not_enough_data:
|
|
{
|
|
GST_ELEMENT_WARNING (rmdemux, STREAM, DECODE, ("Skipping bad packet."),
|
|
(NULL));
|
|
gst_buffer_unmap (in, &map);
|
|
gst_buffer_unref (in);
|
|
return GST_FLOW_OK;
|
|
}
|
|
}
|