blob: 3204031bdbc4e7530de57a89cc0345ebd6e3bd38 [file] [log] [blame]
/* GStreamer ASF/WMV/WMA demuxer
* Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) 2006-2009 Tim-Philipp Müller <tim centricular net>
*
* 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.
*/
/* TODO:
*
* - _loop():
* stop if at end of segment if != end of file, ie. demux->segment.stop
*
* - fix packet parsing:
* there's something wrong with timestamps for packets with keyframes,
* and durations too.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gstutils.h>
#include <gst/base/gstbytereader.h>
#include <gst/base/gsttypefindhelper.h>
#include <gst/riff/riff-media.h>
#include <gst/tag/tag.h>
#include <gst/gst-i18n-plugin.h>
#include <gst/video/video.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gstasfdemux.h"
#include "asfheaders.h"
#include "asfpacket.h"
static GstStaticPadTemplate gst_asf_demux_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-ms-asf")
);
static GstStaticPadTemplate audio_src_template =
GST_STATIC_PAD_TEMPLATE ("audio_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate video_src_template =
GST_STATIC_PAD_TEMPLATE ("video_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
/* size of an ASF object header, ie. GUID (16 bytes) + object size (8 bytes) */
#define ASF_OBJECT_HEADER_SIZE (16+8)
/* FIXME: get rid of this */
/* abuse this GstFlowReturn enum for internal usage */
#define ASF_FLOW_NEED_MORE_DATA 99
#define gst_asf_get_flow_name(flow) \
(flow == ASF_FLOW_NEED_MORE_DATA) ? \
"need-more-data" : gst_flow_get_name (flow)
GST_DEBUG_CATEGORY (asfdemux_dbg);
static GstStateChangeReturn gst_asf_demux_change_state (GstElement * element,
GstStateChange transition);
static gboolean gst_asf_demux_element_send_event (GstElement * element,
GstEvent * event);
static gboolean gst_asf_demux_send_event_unlocked (GstASFDemux * demux,
GstEvent * event);
static gboolean gst_asf_demux_handle_src_query (GstPad * pad,
GstObject * parent, GstQuery * query);
static GstFlowReturn gst_asf_demux_chain (GstPad * pad, GstObject * parent,
GstBuffer * buf);
static gboolean gst_asf_demux_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static GstFlowReturn gst_asf_demux_process_object (GstASFDemux * demux,
guint8 ** p_data, guint64 * p_size);
static gboolean gst_asf_demux_activate (GstPad * sinkpad, GstObject * parent);
static gboolean gst_asf_demux_activate_mode (GstPad * sinkpad,
GstObject * parent, GstPadMode mode, gboolean active);
static void gst_asf_demux_loop (GstASFDemux * demux);
static void
gst_asf_demux_process_queued_extended_stream_objects (GstASFDemux * demux);
static gboolean gst_asf_demux_pull_headers (GstASFDemux * demux,
GstFlowReturn * pflow);
static GstFlowReturn gst_asf_demux_pull_indices (GstASFDemux * demux);
static void gst_asf_demux_reset_stream_state_after_discont (GstASFDemux * asf);
static gboolean
gst_asf_demux_parse_data_object_start (GstASFDemux * demux, guint8 * data);
static void gst_asf_demux_descramble_buffer (GstASFDemux * demux,
AsfStream * stream, GstBuffer ** p_buffer);
static void gst_asf_demux_activate_stream (GstASFDemux * demux,
AsfStream * stream);
static GstStructure *gst_asf_demux_get_metadata_for_stream (GstASFDemux * d,
guint stream_num);
static GstFlowReturn gst_asf_demux_push_complete_payloads (GstASFDemux * demux,
gboolean force);
#define gst_asf_demux_parent_class parent_class
G_DEFINE_TYPE (GstASFDemux, gst_asf_demux, GST_TYPE_ELEMENT);
static void
gst_asf_demux_class_init (GstASFDemuxClass * klass)
{
GstElementClass *gstelement_class;
gstelement_class = (GstElementClass *) klass;
gst_element_class_set_static_metadata (gstelement_class, "ASF Demuxer",
"Codec/Demuxer",
"Demultiplexes ASF Streams", "Owen Fraser-Green <owen@discobabe.net>");
gst_element_class_add_static_pad_template (gstelement_class,
&audio_src_template);
gst_element_class_add_static_pad_template (gstelement_class,
&video_src_template);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_asf_demux_sink_template);
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_asf_demux_change_state);
gstelement_class->send_event =
GST_DEBUG_FUNCPTR (gst_asf_demux_element_send_event);
}
static void
gst_asf_demux_free_stream (GstASFDemux * demux, AsfStream * stream)
{
gst_caps_replace (&stream->caps, NULL);
if (stream->pending_tags) {
gst_tag_list_unref (stream->pending_tags);
stream->pending_tags = NULL;
}
if (stream->streamheader) {
gst_buffer_unref (stream->streamheader);
stream->streamheader = NULL;
}
if (stream->pad) {
if (stream->active) {
gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad);
gst_flow_combiner_remove_pad (demux->flowcombiner, stream->pad);
} else
gst_object_unref (stream->pad);
stream->pad = NULL;
}
if (stream->payloads) {
while (stream->payloads->len > 0) {
AsfPayload *payload;
guint last;
last = stream->payloads->len - 1;
payload = &g_array_index (stream->payloads, AsfPayload, last);
gst_buffer_replace (&payload->buf, NULL);
g_array_remove_index (stream->payloads, last);
}
g_array_free (stream->payloads, TRUE);
stream->payloads = NULL;
}
if (stream->payloads_rev) {
while (stream->payloads_rev->len > 0) {
AsfPayload *payload;
guint last;
last = stream->payloads_rev->len - 1;
payload = &g_array_index (stream->payloads_rev, AsfPayload, last);
gst_buffer_replace (&payload->buf, NULL);
g_array_remove_index (stream->payloads_rev, last);
}
g_array_free (stream->payloads_rev, TRUE);
stream->payloads_rev = NULL;
}
if (stream->ext_props.valid) {
g_free (stream->ext_props.payload_extensions);
stream->ext_props.payload_extensions = NULL;
}
}
static void
gst_asf_demux_reset (GstASFDemux * demux, gboolean chain_reset)
{
GST_LOG_OBJECT (demux, "resetting");
gst_segment_init (&demux->segment, GST_FORMAT_UNDEFINED);
demux->segment_running = FALSE;
if (demux->adapter && !chain_reset) {
gst_adapter_clear (demux->adapter);
g_object_unref (demux->adapter);
demux->adapter = NULL;
}
if (demux->taglist) {
gst_tag_list_unref (demux->taglist);
demux->taglist = NULL;
}
if (demux->metadata) {
gst_caps_unref (demux->metadata);
demux->metadata = NULL;
}
if (demux->global_metadata) {
gst_structure_free (demux->global_metadata);
demux->global_metadata = NULL;
}
if (demux->mut_ex_streams) {
g_slist_free (demux->mut_ex_streams);
demux->mut_ex_streams = NULL;
}
demux->state = GST_ASF_DEMUX_STATE_HEADER;
g_free (demux->objpath);
demux->objpath = NULL;
g_strfreev (demux->languages);
demux->languages = NULL;
demux->num_languages = 0;
g_slist_foreach (demux->ext_stream_props, (GFunc) gst_mini_object_unref,
NULL);
g_slist_free (demux->ext_stream_props);
demux->ext_stream_props = NULL;
while (demux->old_num_streams > 0) {
gst_asf_demux_free_stream (demux,
&demux->old_stream[demux->old_num_streams - 1]);
--demux->old_num_streams;
}
memset (demux->old_stream, 0, sizeof (demux->old_stream));
demux->old_num_streams = 0;
/* when resetting for a new chained asf, we don't want to remove the pads
* before adding the new ones */
if (chain_reset) {
memcpy (demux->old_stream, demux->stream, sizeof (demux->stream));
demux->old_num_streams = demux->num_streams;
demux->num_streams = 0;
}
while (demux->num_streams > 0) {
gst_asf_demux_free_stream (demux, &demux->stream[demux->num_streams - 1]);
--demux->num_streams;
}
memset (demux->stream, 0, sizeof (demux->stream));
if (!chain_reset) {
/* do not remove those for not adding pads with same name */
demux->num_audio_streams = 0;
demux->num_video_streams = 0;
demux->have_group_id = FALSE;
demux->group_id = G_MAXUINT;
}
demux->num_streams = 0;
demux->activated_streams = FALSE;
demux->first_ts = GST_CLOCK_TIME_NONE;
demux->segment_ts = GST_CLOCK_TIME_NONE;
demux->in_gap = 0;
if (!chain_reset)
gst_segment_init (&demux->in_segment, GST_FORMAT_UNDEFINED);
demux->state = GST_ASF_DEMUX_STATE_HEADER;
demux->seekable = FALSE;
demux->broadcast = FALSE;
demux->sidx_interval = 0;
demux->sidx_num_entries = 0;
g_free (demux->sidx_entries);
demux->sidx_entries = NULL;
demux->speed_packets = 1;
demux->asf_3D_mode = GST_ASF_3D_NONE;
if (chain_reset) {
GST_LOG_OBJECT (demux, "Restarting");
gst_segment_init (&demux->segment, GST_FORMAT_TIME);
demux->need_newsegment = TRUE;
demux->segment_seqnum = 0;
demux->segment_running = FALSE;
demux->keyunit_sync = FALSE;
demux->accurate = FALSE;
demux->metadata = gst_caps_new_empty ();
demux->global_metadata = gst_structure_new_empty ("metadata");
demux->data_size = 0;
demux->data_offset = 0;
demux->index_offset = 0;
} else {
demux->base_offset = 0;
}
g_slist_free (demux->other_streams);
demux->other_streams = NULL;
}
static void
gst_asf_demux_init (GstASFDemux * demux)
{
demux->sinkpad =
gst_pad_new_from_static_template (&gst_asf_demux_sink_template, "sink");
gst_pad_set_chain_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_asf_demux_chain));
gst_pad_set_event_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_asf_demux_sink_event));
gst_pad_set_activate_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_asf_demux_activate));
gst_pad_set_activatemode_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_asf_demux_activate_mode));
gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad);
/* set initial state */
gst_asf_demux_reset (demux, FALSE);
}
static gboolean
gst_asf_demux_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_asf_demux_activate_mode (GstPad * sinkpad, GstObject * parent,
GstPadMode mode, gboolean active)
{
gboolean res;
GstASFDemux *demux;
demux = GST_ASF_DEMUX (parent);
switch (mode) {
case GST_PAD_MODE_PUSH:
demux->state = GST_ASF_DEMUX_STATE_HEADER;
demux->streaming = TRUE;
res = TRUE;
break;
case GST_PAD_MODE_PULL:
if (active) {
demux->state = GST_ASF_DEMUX_STATE_HEADER;
demux->streaming = FALSE;
res = gst_pad_start_task (sinkpad, (GstTaskFunction) gst_asf_demux_loop,
demux, NULL);
} else {
res = gst_pad_stop_task (sinkpad);
}
break;
default:
res = FALSE;
break;
}
return res;
}
static gboolean
gst_asf_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstASFDemux *demux;
gboolean ret = TRUE;
demux = GST_ASF_DEMUX (parent);
GST_LOG_OBJECT (demux, "handling %s event", GST_EVENT_TYPE_NAME (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEGMENT:{
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
if (segment->format == GST_FORMAT_BYTES) {
if (demux->packet_size && segment->start > demux->data_offset)
demux->packet = (segment->start - demux->data_offset) /
demux->packet_size;
else
demux->packet = 0;
} else if (segment->format == GST_FORMAT_TIME) {
/* do not know packet position, not really a problem */
demux->packet = -1;
} else {
GST_WARNING_OBJECT (demux, "unsupported newsegment format, ignoring");
gst_event_unref (event);
break;
}
/* record upstream segment for interpolation */
if (segment->format != demux->in_segment.format)
gst_segment_init (&demux->in_segment, GST_FORMAT_UNDEFINED);
gst_segment_copy_into (segment, &demux->in_segment);
/* in either case, clear some state and generate newsegment later on */
GST_OBJECT_LOCK (demux);
demux->segment_ts = GST_CLOCK_TIME_NONE;
demux->in_gap = GST_CLOCK_TIME_NONE;
demux->need_newsegment = TRUE;
demux->segment_seqnum = gst_event_get_seqnum (event);
gst_asf_demux_reset_stream_state_after_discont (demux);
/* if we seek back after reaching EOS, go back to packet reading state */
if (demux->data_offset > 0 && segment->start >= demux->data_offset
&& demux->state == GST_ASF_DEMUX_STATE_INDEX) {
demux->state = GST_ASF_DEMUX_STATE_DATA;
}
GST_OBJECT_UNLOCK (demux);
gst_event_unref (event);
break;
}
case GST_EVENT_EOS:{
GstFlowReturn flow;
if (demux->state == GST_ASF_DEMUX_STATE_HEADER) {
GST_ELEMENT_ERROR (demux, STREAM, DEMUX,
(_("This stream contains no data.")),
("got eos and didn't receive a complete header object"));
break;
}
flow = gst_asf_demux_push_complete_payloads (demux, TRUE);
if (!demux->activated_streams) {
/* If we still haven't got activated streams, the file is most likely corrupt */
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE,
(_("This stream contains no data.")),
("got eos and didn't receive a complete header object"));
break;
}
if (flow < GST_FLOW_EOS || flow == GST_FLOW_NOT_LINKED) {
GST_ELEMENT_FLOW_ERROR (demux, flow);
break;
}
GST_OBJECT_LOCK (demux);
gst_adapter_clear (demux->adapter);
GST_OBJECT_UNLOCK (demux);
gst_asf_demux_send_event_unlocked (demux, event);
break;
}
case GST_EVENT_FLUSH_STOP:
GST_OBJECT_LOCK (demux);
gst_asf_demux_reset_stream_state_after_discont (demux);
GST_OBJECT_UNLOCK (demux);
gst_asf_demux_send_event_unlocked (demux, event);
/* upon activation, latency is no longer introduced, e.g. after seek */
if (demux->activated_streams)
demux->latency = 0;
break;
default:
ret = gst_pad_event_default (pad, parent, event);
break;
}
return ret;
}
static gboolean
gst_asf_demux_seek_index_lookup (GstASFDemux * demux, guint * packet,
GstClockTime seek_time, GstClockTime * p_idx_time, guint * speed,
gboolean next, gboolean * eos)
{
GstClockTime idx_time;
guint idx;
if (eos)
*eos = FALSE;
if (G_UNLIKELY (demux->sidx_num_entries == 0 || demux->sidx_interval == 0))
return FALSE;
idx = (guint) ((seek_time + demux->preroll) / demux->sidx_interval);
if (next) {
/* if we want the next keyframe, we have to go forward till we find
a different packet number */
guint idx2;
if (idx >= demux->sidx_num_entries - 1) {
/* If we get here, we're asking for next keyframe after the last one. There isn't one. */
if (eos)
*eos = TRUE;
return FALSE;
}
for (idx2 = idx + 1; idx2 < demux->sidx_num_entries; ++idx2) {
if (demux->sidx_entries[idx].packet != demux->sidx_entries[idx2].packet) {
idx = idx2;
break;
}
}
}
if (G_UNLIKELY (idx >= demux->sidx_num_entries)) {
if (eos)
*eos = TRUE;
return FALSE;
}
*packet = demux->sidx_entries[idx].packet;
if (speed)
*speed = demux->sidx_entries[idx].count;
/* so we get closer to the actual time of the packet ... actually, let's not
* do this, since we throw away superfluous payloads before the seek position
* anyway; this way, our key unit seek 'snap resolution' is a bit better
* (ie. same as index resolution) */
/*
while (idx > 0 && demux->sidx_entries[idx-1] == demux->sidx_entries[idx])
--idx;
*/
idx_time = demux->sidx_interval * idx;
if (G_LIKELY (idx_time >= demux->preroll))
idx_time -= demux->preroll;
GST_DEBUG_OBJECT (demux, "%" GST_TIME_FORMAT " => packet %u at %"
GST_TIME_FORMAT, GST_TIME_ARGS (seek_time), *packet,
GST_TIME_ARGS (idx_time));
if (G_LIKELY (p_idx_time))
*p_idx_time = idx_time;
return TRUE;
}
static void
gst_asf_demux_reset_stream_state_after_discont (GstASFDemux * demux)
{
guint n;
gst_adapter_clear (demux->adapter);
GST_DEBUG_OBJECT (demux, "reset stream state");
gst_flow_combiner_reset (demux->flowcombiner);
for (n = 0; n < demux->num_streams; n++) {
demux->stream[n].discont = TRUE;
demux->stream[n].first_buffer = TRUE;
while (demux->stream[n].payloads->len > 0) {
AsfPayload *payload;
guint last;
last = demux->stream[n].payloads->len - 1;
payload = &g_array_index (demux->stream[n].payloads, AsfPayload, last);
gst_buffer_replace (&payload->buf, NULL);
g_array_remove_index (demux->stream[n].payloads, last);
}
}
}
static void
gst_asf_demux_mark_discont (GstASFDemux * demux)
{
guint n;
GST_DEBUG_OBJECT (demux, "Mark stream discont");
for (n = 0; n < demux->num_streams; n++)
demux->stream[n].discont = TRUE;
}
/* do a seek in push based mode */
static gboolean
gst_asf_demux_handle_seek_push (GstASFDemux * demux, GstEvent * event)
{
gdouble rate;
GstFormat format;
GstSeekFlags flags;
GstSeekType cur_type, stop_type;
gint64 cur, stop;
guint packet;
gboolean res;
GstEvent *byte_event;
gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur,
&stop_type, &stop);
stop_type = GST_SEEK_TYPE_NONE;
stop = -1;
GST_DEBUG_OBJECT (demux, "seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (cur));
/* determine packet, by index or by estimation */
if (!gst_asf_demux_seek_index_lookup (demux, &packet, cur, NULL, NULL, FALSE,
NULL)) {
packet =
(guint) gst_util_uint64_scale (demux->num_packets, cur,
demux->play_time);
}
if (packet > demux->num_packets) {
GST_DEBUG_OBJECT (demux, "could not determine packet to seek to, "
"seek aborted.");
return FALSE;
}
GST_DEBUG_OBJECT (demux, "seeking to packet %d", packet);
cur = demux->data_offset + ((guint64) packet * demux->packet_size);
GST_DEBUG_OBJECT (demux, "Pushing BYTE seek rate %g, "
"start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT, rate, cur, stop);
/* BYTE seek event */
byte_event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type,
cur, stop_type, stop);
gst_event_set_seqnum (byte_event, gst_event_get_seqnum (event));
res = gst_pad_push_event (demux->sinkpad, byte_event);
return res;
}
static gboolean
gst_asf_demux_handle_seek_event (GstASFDemux * demux, GstEvent * event)
{
GstClockTime idx_time;
GstSegment segment;
GstSeekFlags flags;
GstSeekType cur_type, stop_type;
GstFormat format;
gboolean only_need_update;
gboolean after, before, next;
gboolean flush;
gdouble rate;
gint64 cur, stop;
gint64 seek_time;
guint packet, speed_count = 1;
gboolean eos;
guint32 seqnum;
GstEvent *fevent;
gint i;
gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur,
&stop_type, &stop);
if (G_UNLIKELY (format != GST_FORMAT_TIME)) {
GST_LOG_OBJECT (demux, "seeking is only supported in TIME format");
return FALSE;
}
/* upstream might handle TIME seek, e.g. mms or rtsp, or not, e.g. http,
* so first try to let it handle the seek event. */
if (gst_pad_push_event (demux->sinkpad, gst_event_ref (event)))
return TRUE;
if (G_UNLIKELY (demux->seekable == FALSE || demux->packet_size == 0 ||
demux->num_packets == 0 || demux->play_time == 0)) {
GST_LOG_OBJECT (demux, "stream is not seekable");
return FALSE;
}
if (G_UNLIKELY (!demux->activated_streams)) {
GST_LOG_OBJECT (demux, "streams not yet activated, ignoring seek");
return FALSE;
}
if (G_UNLIKELY (rate <= 0.0)) {
GST_LOG_OBJECT (demux, "backward playback");
demux->seek_to_cur_pos = TRUE;
for (i = 0; i < demux->num_streams; i++) {
demux->stream[i].reverse_kf_ready = FALSE;
}
}
seqnum = gst_event_get_seqnum (event);
flush = ((flags & GST_SEEK_FLAG_FLUSH) == GST_SEEK_FLAG_FLUSH);
demux->accurate =
((flags & GST_SEEK_FLAG_ACCURATE) == GST_SEEK_FLAG_ACCURATE);
demux->keyunit_sync =
((flags & GST_SEEK_FLAG_KEY_UNIT) == GST_SEEK_FLAG_KEY_UNIT);
after = ((flags & GST_SEEK_FLAG_SNAP_AFTER) == GST_SEEK_FLAG_SNAP_AFTER);
before = ((flags & GST_SEEK_FLAG_SNAP_BEFORE) == GST_SEEK_FLAG_SNAP_BEFORE);
next = after && !before;
if (G_UNLIKELY (demux->streaming)) {
/* support it safely needs more segment handling, e.g. closing etc */
if (!flush) {
GST_LOG_OBJECT (demux, "streaming; non-flushing seek not supported");
return FALSE;
}
/* we can (re)construct the start later on, but not the end */
if (stop_type != GST_SEEK_TYPE_NONE &&
(stop_type != GST_SEEK_TYPE_SET || GST_CLOCK_TIME_IS_VALID (stop))) {
GST_LOG_OBJECT (demux, "streaming; end position must be NONE");
return FALSE;
}
return gst_asf_demux_handle_seek_push (demux, event);
}
/* unlock the streaming thread */
if (G_LIKELY (flush)) {
fevent = gst_event_new_flush_start ();
gst_event_set_seqnum (fevent, seqnum);
gst_pad_push_event (demux->sinkpad, gst_event_ref (fevent));
gst_asf_demux_send_event_unlocked (demux, fevent);
} else {
gst_pad_pause_task (demux->sinkpad);
}
/* 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 (demux->sinkpad);
if (G_LIKELY (flush)) {
/* we now can stop flushing, since we have the stream lock now */
fevent = gst_event_new_flush_stop (TRUE);
gst_event_set_seqnum (fevent, seqnum);
gst_pad_push_event (demux->sinkpad, gst_event_ref (fevent));
gst_asf_demux_send_event_unlocked (demux, fevent);
}
/* operating on copy of segment until we know the seek worked */
segment = demux->segment;
gst_segment_do_seek (&segment, rate, format, flags, cur_type,
cur, stop_type, stop, &only_need_update);
GST_DEBUG_OBJECT (demux, "seeking to time %" GST_TIME_FORMAT ", segment: "
"%" GST_SEGMENT_FORMAT, GST_TIME_ARGS (segment.start), &segment);
if (cur_type != GST_SEEK_TYPE_SET)
seek_time = segment.start;
else
seek_time = cur;
/* FIXME: should check the KEY_UNIT flag; need to adjust position to
* real start of data and segment_start to indexed time for key unit seek*/
if (G_UNLIKELY (!gst_asf_demux_seek_index_lookup (demux, &packet, seek_time,
&idx_time, &speed_count, next, &eos))) {
gint64 offset;
if (eos) {
demux->packet = demux->num_packets;
goto skip;
}
/* First try to query our source to see if it can convert for us. This is
the case when our source is an mms stream, notice that in this case
gstmms will do a time based seek to get the byte offset, this is not a
problem as the seek to this offset needs to happen anway. */
if (gst_pad_peer_query_convert (demux->sinkpad, GST_FORMAT_TIME, seek_time,
GST_FORMAT_BYTES, &offset)) {
packet = (offset - demux->data_offset) / demux->packet_size;
GST_LOG_OBJECT (demux, "convert %" GST_TIME_FORMAT
" to bytes query result: %" G_GINT64_FORMAT ", data_ofset: %"
G_GINT64_FORMAT ", packet_size: %u," " resulting packet: %u\n",
GST_TIME_ARGS (seek_time), offset, demux->data_offset,
demux->packet_size, packet);
} else {
/* FIXME: For streams containing video, seek to an earlier position in
* the hope of hitting a keyframe and let the sinks throw away the stuff
* before the segment start. For audio-only this is unnecessary as every
* frame is 'key'. */
if (flush && (demux->accurate || (demux->keyunit_sync && !next))
&& demux->num_video_streams > 0) {
seek_time -= 5 * GST_SECOND;
if (seek_time < 0)
seek_time = 0;
}
packet = (guint) gst_util_uint64_scale (demux->num_packets,
seek_time, demux->play_time);
if (packet > demux->num_packets)
packet = demux->num_packets;
}
} else {
if (G_LIKELY (demux->keyunit_sync && !demux->accurate)) {
GST_DEBUG_OBJECT (demux, "key unit seek, adjust seek_time = %"
GST_TIME_FORMAT " to index_time = %" GST_TIME_FORMAT,
GST_TIME_ARGS (seek_time), GST_TIME_ARGS (idx_time));
segment.start = idx_time;
segment.position = idx_time;
segment.time = idx_time;
}
}
GST_DEBUG_OBJECT (demux, "seeking to packet %u (%d)", packet, speed_count);
GST_OBJECT_LOCK (demux);
demux->segment = segment;
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
demux->packet = (gint64) gst_util_uint64_scale (demux->num_packets,
stop, demux->play_time);
} else {
demux->packet = packet;
}
demux->need_newsegment = TRUE;
demux->segment_seqnum = seqnum;
demux->speed_packets =
GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment) ? 1 : speed_count;
gst_asf_demux_reset_stream_state_after_discont (demux);
GST_OBJECT_UNLOCK (demux);
skip:
/* restart our task since it might have been stopped when we did the flush */
gst_pad_start_task (demux->sinkpad, (GstTaskFunction) gst_asf_demux_loop,
demux, NULL);
/* streaming can continue now */
GST_PAD_STREAM_UNLOCK (demux->sinkpad);
return TRUE;
}
static gboolean
gst_asf_demux_handle_src_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
GstASFDemux *demux;
gboolean ret;
demux = GST_ASF_DEMUX (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
GST_LOG_OBJECT (pad, "seek event");
ret = gst_asf_demux_handle_seek_event (demux, event);
gst_event_unref (event);
break;
case GST_EVENT_QOS:
case GST_EVENT_NAVIGATION:
/* just drop these two silently */
gst_event_unref (event);
ret = FALSE;
break;
default:
GST_LOG_OBJECT (pad, "%s event", GST_EVENT_TYPE_NAME (event));
ret = gst_pad_event_default (pad, parent, event);
break;
}
return ret;
}
static inline guint32
gst_asf_demux_identify_guid (const ASFGuidHash * guids, ASFGuid * guid)
{
guint32 ret;
ret = gst_asf_identify_guid (guids, guid);
GST_LOG ("%s 0x%08x-0x%08x-0x%08x-0x%08x",
gst_asf_get_guid_nick (guids, ret),
guid->v1, guid->v2, guid->v3, guid->v4);
return ret;
}
typedef struct
{
AsfObjectID id;
guint64 size;
} AsfObject;
/* Peek for an object.
*
* Returns FALSE is the object is corrupted (such as the reported
* object size being greater than 2**32bits.
*/
static gboolean
asf_demux_peek_object (GstASFDemux * demux, const guint8 * data,
guint data_len, AsfObject * object, gboolean expect)
{
ASFGuid guid;
/* Callers should have made sure that data_len is big enough */
g_assert (data_len >= ASF_OBJECT_HEADER_SIZE);
if (data_len < ASF_OBJECT_HEADER_SIZE)
return FALSE;
guid.v1 = GST_READ_UINT32_LE (data + 0);
guid.v2 = GST_READ_UINT32_LE (data + 4);
guid.v3 = GST_READ_UINT32_LE (data + 8);
guid.v4 = GST_READ_UINT32_LE (data + 12);
/* FIXME: make asf_demux_identify_object_guid() */
object->id = gst_asf_demux_identify_guid (asf_object_guids, &guid);
if (object->id == ASF_OBJ_UNDEFINED && expect) {
GST_WARNING_OBJECT (demux, "Unknown object %08x-%08x-%08x-%08x",
guid.v1, guid.v2, guid.v3, guid.v4);
}
object->size = GST_READ_UINT64_LE (data + 16);
if (object->id != ASF_OBJ_DATA && object->size >= G_MAXUINT) {
GST_WARNING_OBJECT (demux,
"ASF Object size corrupted (greater than 32bit)");
return FALSE;
}
return TRUE;
}
static void
gst_asf_demux_release_old_pads (GstASFDemux * demux)
{
GST_DEBUG_OBJECT (demux, "Releasing old pads");
while (demux->old_num_streams > 0) {
gst_pad_push_event (demux->old_stream[demux->old_num_streams - 1].pad,
gst_event_new_eos ());
gst_asf_demux_free_stream (demux,
&demux->old_stream[demux->old_num_streams - 1]);
--demux->old_num_streams;
}
memset (demux->old_stream, 0, sizeof (demux->old_stream));
demux->old_num_streams = 0;
}
static GstFlowReturn
gst_asf_demux_chain_headers (GstASFDemux * demux)
{
AsfObject obj;
guint8 *header_data, *data = NULL;
const guint8 *cdata = NULL;
guint64 header_size;
GstFlowReturn flow = GST_FLOW_OK;
cdata = (guint8 *) gst_adapter_map (demux->adapter, ASF_OBJECT_HEADER_SIZE);
if (cdata == NULL)
goto need_more_data;
if (!asf_demux_peek_object (demux, cdata, ASF_OBJECT_HEADER_SIZE, &obj, TRUE))
goto parse_failed;
if (obj.id != ASF_OBJ_HEADER)
goto wrong_type;
GST_LOG_OBJECT (demux, "header size = %u", (guint) obj.size);
/* + 50 for non-packet data at beginning of ASF_OBJ_DATA */
if (gst_adapter_available (demux->adapter) < obj.size + 50)
goto need_more_data;
data = gst_adapter_take (demux->adapter, obj.size + 50);
header_data = data;
header_size = obj.size;
flow = gst_asf_demux_process_object (demux, &header_data, &header_size);
if (flow != GST_FLOW_OK)
goto parse_failed;
/* calculate where the packet data starts */
demux->data_offset = obj.size + 50;
/* now parse the beginning of the ASF_OBJ_DATA object */
if (!gst_asf_demux_parse_data_object_start (demux, data + obj.size))
goto wrong_type;
if (demux->num_streams == 0)
goto no_streams;
g_free (data);
return GST_FLOW_OK;
/* NON-FATAL */
need_more_data:
{
GST_LOG_OBJECT (demux, "not enough data in adapter yet");
return GST_FLOW_OK;
}
/* ERRORS */
wrong_type:
{
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL),
("This doesn't seem to be an ASF file"));
g_free (data);
return GST_FLOW_ERROR;
}
no_streams:
parse_failed:
{
GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL),
("header parsing failed, or no streams found, flow = %s",
gst_flow_get_name (flow)));
g_free (data);
return GST_FLOW_ERROR;
}
}
static gboolean
gst_asf_demux_pull_data (GstASFDemux * demux, guint64 offset, guint size,
GstBuffer ** p_buf, GstFlowReturn * p_flow)
{
gsize buffer_size;
GstFlowReturn flow;
GST_LOG_OBJECT (demux, "pulling buffer at %" G_GUINT64_FORMAT "+%u",
offset, size);
flow = gst_pad_pull_range (demux->sinkpad, offset, size, p_buf);
if (G_LIKELY (p_flow))
*p_flow = flow;
if (G_UNLIKELY (flow != GST_FLOW_OK)) {
GST_DEBUG_OBJECT (demux, "flow %s pulling buffer at %" G_GUINT64_FORMAT
"+%u", gst_flow_get_name (flow), offset, size);
*p_buf = NULL;
return FALSE;
}
g_assert (*p_buf != NULL);
buffer_size = gst_buffer_get_size (*p_buf);
if (G_UNLIKELY (buffer_size < size)) {
GST_DEBUG_OBJECT (demux, "short read pulling buffer at %" G_GUINT64_FORMAT
"+%u (got only %" G_GSIZE_FORMAT " bytes)", offset, size, buffer_size);
gst_buffer_unref (*p_buf);
if (G_LIKELY (p_flow))
*p_flow = GST_FLOW_EOS;
*p_buf = NULL;
return FALSE;
}
return TRUE;
}
static GstFlowReturn
gst_asf_demux_pull_indices (GstASFDemux * demux)
{
GstBuffer *buf = NULL;
guint64 offset;
guint num_read = 0;
GstFlowReturn ret = GST_FLOW_OK;
offset = demux->index_offset;
if (G_UNLIKELY (offset == 0)) {
GST_DEBUG_OBJECT (demux, "can't read indices, don't know index offset");
/* non-fatal */
return GST_FLOW_OK;
}
while (gst_asf_demux_pull_data (demux, offset, 16 + 8, &buf, NULL)) {
AsfObject obj;
GstMapInfo map;
guint8 *bufdata;
guint64 obj_size;
gst_buffer_map (buf, &map, GST_MAP_READ);
g_assert (map.size >= 16 + 8);
if (!asf_demux_peek_object (demux, map.data, 16 + 8, &obj, TRUE)) {
GST_DEBUG_OBJECT (demux, "No valid object, corrupted index, ignoring");
GST_MEMDUMP_OBJECT (demux, "Corrupted index ?", map.data, MIN (map.size,
64));
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
/* Non-fatal, return */
break;
}
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
/* check for sanity */
if (G_UNLIKELY (obj.size > (5 * 1024 * 1024))) {
GST_DEBUG_OBJECT (demux, "implausible index object size, bailing out");
break;
}
if (G_UNLIKELY (!gst_asf_demux_pull_data (demux, offset, obj.size, &buf,
NULL)))
break;
GST_LOG_OBJECT (demux, "index object at offset 0x%" G_GINT64_MODIFIER "X"
", size %u", offset, (guint) obj.size);
offset += obj.size; /* increase before _process_object changes it */
gst_buffer_map (buf, &map, GST_MAP_READ);
g_assert (map.size >= obj.size);
bufdata = (guint8 *) map.data;
obj_size = obj.size;
ret = gst_asf_demux_process_object (demux, &bufdata, &obj_size);
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
if (ret == ASF_FLOW_NEED_MORE_DATA) {
/* Since indices are at the end of the file, if we need more data,
* we consider it as a non-fatal corrupted index */
ret = GST_FLOW_OK;
break;
}
if (G_UNLIKELY (ret != GST_FLOW_OK))
break;
++num_read;
}
GST_DEBUG_OBJECT (demux, "read %u index objects , returning %s", num_read,
gst_flow_get_name (ret));
return ret;
}
static gboolean
gst_asf_demux_parse_data_object_start (GstASFDemux * demux, guint8 * data)
{
AsfObject obj;
if (!asf_demux_peek_object (demux, data, 50, &obj, TRUE)) {
GST_WARNING_OBJECT (demux, "Corrupted data");
return FALSE;
}
if (obj.id != ASF_OBJ_DATA) {
GST_WARNING_OBJECT (demux, "headers not followed by a DATA object");
return FALSE;
}
demux->state = GST_ASF_DEMUX_STATE_DATA;
if (!demux->broadcast && obj.size > 50) {
demux->data_size = obj.size - 50;
/* CHECKME: for at least one file this is off by +158 bytes?! */
demux->index_offset = demux->data_offset + demux->data_size;
} else {
demux->data_size = 0;
demux->index_offset = 0;
}
demux->packet = 0;
if (!demux->broadcast) {
/* skip object header (24 bytes) and file GUID (16 bytes) */
demux->num_packets = GST_READ_UINT64_LE (data + (16 + 8) + 16);
} else {
demux->num_packets = 0;
}
if (demux->num_packets == 0)
demux->seekable = FALSE;
/* fallback in the unlikely case that headers are inconsistent, can't hurt */
if (demux->data_size == 0 && demux->num_packets > 0) {
demux->data_size = demux->num_packets * demux->packet_size;
demux->index_offset = demux->data_offset + demux->data_size;
}
/* process pending stream objects and create pads for those */
gst_asf_demux_process_queued_extended_stream_objects (demux);
GST_INFO_OBJECT (demux, "Stream has %" G_GUINT64_FORMAT " packets, "
"data_offset=%" G_GINT64_FORMAT ", data_size=%" G_GINT64_FORMAT
", index_offset=%" G_GUINT64_FORMAT, demux->num_packets,
demux->data_offset, demux->data_size, demux->index_offset);
return TRUE;
}
static gboolean
gst_asf_demux_pull_headers (GstASFDemux * demux, GstFlowReturn * pflow)
{
GstFlowReturn flow = GST_FLOW_OK;
AsfObject obj;
GstBuffer *buf = NULL;
guint64 size;
GstMapInfo map;
guint8 *bufdata;
GST_LOG_OBJECT (demux, "reading headers");
/* pull HEADER object header, so we know its size */
if (!gst_asf_demux_pull_data (demux, demux->base_offset, 16 + 8, &buf, &flow))
goto read_failed;
gst_buffer_map (buf, &map, GST_MAP_READ);
g_assert (map.size >= 16 + 8);
if (!asf_demux_peek_object (demux, map.data, 16 + 8, &obj, TRUE)) {
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
flow = GST_FLOW_ERROR;
goto read_failed;
}
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
if (obj.id != ASF_OBJ_HEADER)
goto wrong_type;
GST_LOG_OBJECT (demux, "header size = %" G_GUINT64_FORMAT, obj.size);
/* pull HEADER object */
if (!gst_asf_demux_pull_data (demux, demux->base_offset, obj.size, &buf,
&flow))
goto read_failed;
size = obj.size; /* don't want obj.size changed */
gst_buffer_map (buf, &map, GST_MAP_READ);
g_assert (map.size >= size);
bufdata = (guint8 *) map.data;
flow = gst_asf_demux_process_object (demux, &bufdata, &size);
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
if (flow != GST_FLOW_OK) {
GST_WARNING_OBJECT (demux, "process_object: %s", gst_flow_get_name (flow));
goto parse_failed;
}
/* calculate where the packet data starts */
demux->data_offset = demux->base_offset + obj.size + 50;
/* now pull beginning of DATA object before packet data */
if (!gst_asf_demux_pull_data (demux, demux->base_offset + obj.size, 50, &buf,
&flow))
goto read_failed;
gst_buffer_map (buf, &map, GST_MAP_READ);
g_assert (map.size >= size);
bufdata = (guint8 *) map.data;
if (!gst_asf_demux_parse_data_object_start (demux, bufdata))
goto wrong_type;
if (demux->num_streams == 0)
goto no_streams;
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
return TRUE;
/* ERRORS */
wrong_type:
{
if (buf != NULL) {
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
}
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL),
("This doesn't seem to be an ASF file"));
*pflow = GST_FLOW_ERROR;
return FALSE;
}
no_streams:
flow = GST_FLOW_ERROR;
GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL),
("header parsing failed, or no streams found, flow = %s",
gst_flow_get_name (flow)));
read_failed:
parse_failed:
{
if (buf)
gst_buffer_unmap (buf, &map);
gst_buffer_replace (&buf, NULL);
if (flow == ASF_FLOW_NEED_MORE_DATA)
flow = GST_FLOW_ERROR;
*pflow = flow;
return FALSE;
}
}
static gboolean
all_streams_prerolled (GstASFDemux * demux)
{
GstClockTime preroll_time;
guint i, num_no_data = 0;
AsfStreamType prerolled_types = 0, all_types = 0;
/* Allow at least 500ms of preroll_time */
preroll_time = MAX (demux->preroll, 500 * GST_MSECOND);
/* returns TRUE as long as there isn't a stream which (a) has data queued
* and (b) the timestamp of last piece of data queued is < demux->preroll
* AND there is at least one other stream with data queued */
for (i = 0; i < demux->num_streams; ++i) {
AsfPayload *last_payload = NULL;
AsfStream *stream;
gint last_idx;
stream = &demux->stream[i];
all_types |= stream->type;
if (G_UNLIKELY (stream->payloads->len == 0)) {
++num_no_data;
GST_LOG_OBJECT (stream->pad, "no data queued");
continue;
}
prerolled_types |= stream->type;
/* find last payload with timestamp */
for (last_idx = stream->payloads->len - 1;
last_idx >= 0 && (last_payload == NULL
|| !GST_CLOCK_TIME_IS_VALID (last_payload->ts)); --last_idx) {
last_payload = &g_array_index (stream->payloads, AsfPayload, last_idx);
}
GST_LOG_OBJECT (stream->pad, "checking if %" GST_TIME_FORMAT " > %"
GST_TIME_FORMAT, GST_TIME_ARGS (last_payload->ts),
GST_TIME_ARGS (preroll_time));
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (last_payload->ts)
|| last_payload->ts <= preroll_time)) {
GST_LOG_OBJECT (stream->pad, "not beyond preroll point yet");
return FALSE;
}
}
GST_LOG_OBJECT (demux, "all_types:%d prerolled_types:%d",
all_types, prerolled_types);
/* If streams of each present type have prerolled, we are good to go */
if (all_types != 0 && prerolled_types == all_types)
return TRUE;
if (G_UNLIKELY (num_no_data > 0))
return FALSE;
return TRUE;
}
#if 0
static gboolean
gst_asf_demux_have_mutually_exclusive_active_stream (GstASFDemux * demux,
AsfStream * stream)
{
GSList *l;
for (l = demux->mut_ex_streams; l != NULL; l = l->next) {
guint8 *mes;
/* check for each mutual exclusion group whether it affects this stream */
for (mes = (guint8 *) l->data; mes != NULL && *mes != 0xff; ++mes) {
if (*mes == stream->id) {
/* we are in this group; let's check if we've already activated streams
* that are in the same group (and hence mutually exclusive to this
* one) */
for (mes = (guint8 *) l->data; mes != NULL && *mes != 0xff; ++mes) {
guint i;
for (i = 0; i < demux->num_streams; ++i) {
if (demux->stream[i].id == *mes && demux->stream[i].active) {
GST_LOG_OBJECT (demux, "stream with ID %d is mutually exclusive "
"to already active stream with ID %d", stream->id,
demux->stream[i].id);
return TRUE;
}
}
}
/* we can only be in this group once, let's break out and move on to
* the next mutual exclusion group */
break;
}
}
}
return FALSE;
}
#endif
static void
gst_asf_demux_check_segment_ts (GstASFDemux * demux, GstClockTime payload_ts)
{
/* remember the first queued timestamp for the segment */
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (demux->segment_ts) &&
GST_CLOCK_TIME_IS_VALID (demux->first_ts))) {
GST_DEBUG_OBJECT (demux, "segment ts: %" GST_TIME_FORMAT,
GST_TIME_ARGS (demux->first_ts));
demux->segment_ts = payload_ts;
/* always note, but only determines segment when streaming */
if (demux->streaming)
gst_segment_do_seek (&demux->segment, demux->in_segment.rate,
GST_FORMAT_TIME, (GstSeekFlags) demux->segment.flags,
GST_SEEK_TYPE_SET, demux->segment_ts, GST_SEEK_TYPE_NONE, 0, NULL);
}
}
static gboolean
gst_asf_demux_get_first_ts (GstASFDemux * demux)
{
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (demux->first_ts))) {
GstClockTime first_ts = GST_CLOCK_TIME_NONE;
int i;
/* go trhough each stream, find smallest timestamp */
for (i = 0; i < demux->num_streams; ++i) {
AsfStream *stream;
int j;
GstClockTime stream_min_ts = GST_CLOCK_TIME_NONE;
GstClockTime stream_min_ts2 = GST_CLOCK_TIME_NONE; /* second smallest timestamp */
stream = &demux->stream[i];
for (j = 0; j < stream->payloads->len; ++j) {
AsfPayload *payload = &g_array_index (stream->payloads, AsfPayload, j);
if (GST_CLOCK_TIME_IS_VALID (payload->ts) &&
(!GST_CLOCK_TIME_IS_VALID (stream_min_ts)
|| stream_min_ts > payload->ts)) {
stream_min_ts = payload->ts;
}
if (GST_CLOCK_TIME_IS_VALID (payload->ts) &&
payload->ts > stream_min_ts &&
(!GST_CLOCK_TIME_IS_VALID (stream_min_ts2)
|| stream_min_ts2 > payload->ts)) {
stream_min_ts2 = payload->ts;
}
}
/* there are some DVR ms files where first packet has TS of 0 (instead of -1) while subsequent packets have
regular (singificantly larger) timestamps. If we don't deal with it, we may end up with huge gap in timestamps
which makes playback stuck. The 0 timestamp may also be valid though, if the second packet timestamp continues
from it. I havent found a better way to distinguish between these two, except to set an arbitrary boundary
and disregard the first 0 timestamp if the second timestamp is bigger than the boundary) */
GST_DEBUG_OBJECT (demux,
"stream #%u stream_min_ts %" GST_TIME_FORMAT " stream_min_ts2 %"
GST_TIME_FORMAT, stream->id, GST_TIME_ARGS (stream_min_ts),
GST_TIME_ARGS (stream_min_ts2));
if (stream_min_ts == 0 && stream_min_ts2 > GST_SECOND) /* first timestamp is 0 and second is significantly larger, disregard the 0 */
stream_min_ts = stream_min_ts2;
if (GST_CLOCK_TIME_IS_VALID (stream_min_ts) &&
(!GST_CLOCK_TIME_IS_VALID (first_ts) || first_ts > stream_min_ts))
first_ts = stream_min_ts;
}
if (!GST_CLOCK_TIME_IS_VALID (first_ts)) /* can happen */
first_ts = 0;
demux->first_ts = first_ts;
/* update packets queued before we knew first timestamp */
for (i = 0; i < demux->num_streams; ++i) {
AsfStream *stream;
int j;
stream = &demux->stream[i];
for (j = 0; j < stream->payloads->len; ++j) {
AsfPayload *payload = &g_array_index (stream->payloads, AsfPayload, j);
if (GST_CLOCK_TIME_IS_VALID (payload->ts)) {
if (payload->ts > first_ts)
payload->ts -= first_ts;
else
payload->ts = 0;
}
}
}
}
gst_asf_demux_check_segment_ts (demux, 0);
return TRUE;
}
static gboolean
gst_asf_demux_update_caps_from_payload (GstASFDemux * demux, AsfStream * stream)
{
/* try to determine whether the stream is AC-3 or MPEG; In dvr-ms the codecTag is unreliable
and often set wrong, inspecting the data is the only way that seem to be working */
GstTypeFindProbability prob = GST_TYPE_FIND_NONE;
GstCaps *caps = NULL;
int i;
GstAdapter *adapter = gst_adapter_new ();
for (i = 0; i < stream->payloads->len && prob < GST_TYPE_FIND_LIKELY; ++i) {
const guint8 *data;
AsfPayload *payload;
int len;
payload = &g_array_index (stream->payloads, AsfPayload, i);
gst_adapter_push (adapter, gst_buffer_ref (payload->buf));
len = gst_adapter_available (adapter);
data = gst_adapter_map (adapter, len);
again:
#define MIN_LENGTH 128
/* look for the sync points */
while (TRUE) {
if (len < MIN_LENGTH || /* give typefind something to work on */
(data[0] == 0x0b && data[1] == 0x77) || /* AC-3 sync point */
(data[0] == 0xFF && ((data[1] & 0xF0) >> 4) == 0xF)) /* MPEG sync point */
break;
++data;
--len;
}
gst_caps_take (&caps, gst_type_find_helper_for_data (GST_OBJECT (demux),
data, len, &prob));
if (prob < GST_TYPE_FIND_LIKELY) {
++data;
--len;
if (len > MIN_LENGTH)
/* this wasn't it, look for another sync point */
goto again;
}
gst_adapter_unmap (adapter);
}
gst_object_unref (adapter);
if (caps) {
gst_caps_take (&stream->caps, caps);
return TRUE;
} else {
return FALSE;
}
}
static gboolean
gst_asf_demux_check_activate_streams (GstASFDemux * demux, gboolean force)
{
guint i, actual_streams = 0;
if (demux->activated_streams)
return TRUE;
if (!all_streams_prerolled (demux) && !force) {
GST_DEBUG_OBJECT (demux, "not all streams with data beyond preroll yet");
return FALSE;
}
if (G_UNLIKELY (!gst_asf_demux_get_first_ts (demux)))
return FALSE;
for (i = 0; i < demux->num_streams; ++i) {
AsfStream *stream = &demux->stream[i];
if (stream->payloads->len > 0) {
if (stream->inspect_payload && /* dvr-ms required payload inspection */
!stream->active && /* do not inspect active streams (caps were already set) */
!gst_asf_demux_update_caps_from_payload (demux, stream) && /* failed to determine caps */
stream->payloads->len < 20) { /* if we couldn't determine the caps from 20 packets then just give up and use whatever was in codecTag */
/* try to gather some more data */
return FALSE;
}
/* we don't check mutual exclusion stuff here; either we have data for
* a stream, then we active it, or we don't, then we'll ignore it */
GST_LOG_OBJECT (stream->pad, "is prerolled - activate!");
gst_asf_demux_activate_stream (demux, stream);
actual_streams += 1;
} else {
GST_LOG_OBJECT (stream->pad, "no data, ignoring stream");
}
}
if (actual_streams == 0) {
/* We don't have any streams activated ! */
GST_ERROR_OBJECT (demux, "No streams activated!");
return FALSE;
}
gst_asf_demux_release_old_pads (demux);
demux->activated_streams = TRUE;
GST_LOG_OBJECT (demux, "signalling no more pads");
gst_element_no_more_pads (GST_ELEMENT (demux));
return TRUE;
}
/* returns the stream that has a complete payload with the lowest timestamp
* queued, or NULL (we push things by timestamp because during the internal
* prerolling we might accumulate more data then the external queues can take,
* so we'd lock up if we pushed all accumulated data for stream N in one go) */
static AsfStream *
gst_asf_demux_find_stream_with_complete_payload (GstASFDemux * demux)
{
AsfPayload *best_payload = NULL;
AsfStream *best_stream = NULL;
guint i;
for (i = 0; i < demux->num_streams; ++i) {
AsfStream *stream;
int j;
stream = &demux->stream[i];
/* Don't push any data until we have at least one payload that falls within
* the current segment. This way we can remove out-of-segment payloads that
* don't need to be decoded after a seek, sending only data from the
* keyframe directly before our segment start */
if (stream->payloads->len > 0) {
AsfPayload *payload = NULL;
gint last_idx;
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
/* Reverse playback */
if (stream->is_video) {
/* We have to push payloads from KF to the first frame we accumulated (reverse order) */
if (stream->reverse_kf_ready) {
payload =
&g_array_index (stream->payloads, AsfPayload, stream->kf_pos);
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (payload->ts))) {
/* TODO : remove payload from the list? */
continue;
}
} else {
continue;
}
} else {
/* find first complete payload with timestamp */
for (j = stream->payloads->len - 1;
j >= 0 && (payload == NULL
|| !GST_CLOCK_TIME_IS_VALID (payload->ts)); --j) {
payload = &g_array_index (stream->payloads, AsfPayload, j);
}
/* If there's a complete payload queued for this stream */
if (!gst_asf_payload_is_complete (payload))
continue;
}
} else {
/* find last payload with timestamp */
for (last_idx = stream->payloads->len - 1;
last_idx >= 0 && (payload == NULL
|| !GST_CLOCK_TIME_IS_VALID (payload->ts)); --last_idx) {
payload = &g_array_index (stream->payloads, AsfPayload, last_idx);
}
/* if this is first payload after seek we might need to update the segment */
if (GST_CLOCK_TIME_IS_VALID (payload->ts))
gst_asf_demux_check_segment_ts (demux, payload->ts);
if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (payload->ts) &&
(payload->ts < demux->segment.start))) {
if (G_UNLIKELY ((demux->keyunit_sync) && (!demux->accurate)
&& payload->keyframe)) {
GST_DEBUG_OBJECT (stream->pad,
"Found keyframe, updating segment start to %" GST_TIME_FORMAT,
GST_TIME_ARGS (payload->ts));
demux->segment.start = payload->ts;
demux->segment.time = payload->ts;
} else {
GST_DEBUG_OBJECT (stream->pad, "Last queued payload has timestamp %"
GST_TIME_FORMAT " which is before our segment start %"
GST_TIME_FORMAT ", not pushing yet",
GST_TIME_ARGS (payload->ts),
GST_TIME_ARGS (demux->segment.start));
continue;
}
}
payload = NULL;
/* find first complete payload with timestamp */
for (j = 0;
j < stream->payloads->len && (payload == NULL
|| !GST_CLOCK_TIME_IS_VALID (payload->ts)); ++j) {
payload = &g_array_index (stream->payloads, AsfPayload, j);
}
/* Now see if there's a complete payload queued for this stream */
if (!gst_asf_payload_is_complete (payload))
continue;
}
/* ... and whether its timestamp is lower than the current best */
if (best_stream == NULL || best_payload->ts > payload->ts) {
best_stream = stream;
best_payload = payload;
}
}
}
return best_stream;
}
static GstFlowReturn
gst_asf_demux_push_complete_payloads (GstASFDemux * demux, gboolean force)
{
AsfStream *stream;
GstFlowReturn ret = GST_FLOW_OK;
if (G_UNLIKELY (!demux->activated_streams)) {
if (!gst_asf_demux_check_activate_streams (demux, force))
return GST_FLOW_OK;
/* streams are now activated */
}
while ((stream = gst_asf_demux_find_stream_with_complete_payload (demux))) {
AsfPayload *payload;
GstClockTime timestamp = GST_CLOCK_TIME_NONE;
GstClockTime duration = GST_CLOCK_TIME_NONE;
/* wait until we had a chance to "lock on" some payload's timestamp */
if (G_UNLIKELY (demux->need_newsegment
&& !GST_CLOCK_TIME_IS_VALID (demux->segment_ts)))
return GST_FLOW_OK;
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment) && stream->is_video
&& stream->payloads->len) {
payload = &g_array_index (stream->payloads, AsfPayload, stream->kf_pos);
} else {
payload = &g_array_index (stream->payloads, AsfPayload, 0);
}
/* do we need to send a newsegment event */
if ((G_UNLIKELY (demux->need_newsegment))) {
GstEvent *segment_event;
/* safe default if insufficient upstream info */
if (!GST_CLOCK_TIME_IS_VALID (demux->in_gap))
demux->in_gap = 0;
if (demux->segment.stop == GST_CLOCK_TIME_NONE &&
demux->segment.duration > 0) {
/* slight HACK; prevent clipping of last bit */
demux->segment.stop = demux->segment.duration + demux->in_gap;
}
/* FIXME : only if ACCURATE ! */
if (G_LIKELY (demux->keyunit_sync && !demux->accurate
&& (GST_CLOCK_TIME_IS_VALID (payload->ts)))
&& !GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
GST_DEBUG ("Adjusting newsegment start to %" GST_TIME_FORMAT,
GST_TIME_ARGS (payload->ts));
demux->segment.start = payload->ts;
demux->segment.time = payload->ts;
}
GST_DEBUG_OBJECT (demux, "sending new-segment event %" GST_SEGMENT_FORMAT,
&demux->segment);
/* note: we fix up all timestamps to start from 0, so this should be ok */
segment_event = gst_event_new_segment (&demux->segment);
if (demux->segment_seqnum)
gst_event_set_seqnum (segment_event, demux->segment_seqnum);
gst_asf_demux_send_event_unlocked (demux, segment_event);
/* now post any global tags we may have found */
if (demux->taglist == NULL) {
demux->taglist = gst_tag_list_new_empty ();
gst_tag_list_set_scope (demux->taglist, GST_TAG_SCOPE_GLOBAL);
}
gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
GST_TAG_CONTAINER_FORMAT, "ASF", NULL);
GST_DEBUG_OBJECT (demux, "global tags: %" GST_PTR_FORMAT, demux->taglist);
gst_asf_demux_send_event_unlocked (demux,
gst_event_new_tag (demux->taglist));
demux->taglist = NULL;
demux->need_newsegment = FALSE;
demux->segment_seqnum = 0;
demux->segment_running = TRUE;
}
/* Do we have tags pending for this stream? */
if (G_UNLIKELY (stream->pending_tags)) {
GST_LOG_OBJECT (stream->pad, "%" GST_PTR_FORMAT, stream->pending_tags);
gst_pad_push_event (stream->pad,
gst_event_new_tag (stream->pending_tags));
stream->pending_tags = NULL;
}
/* We have the whole packet now so we should push the packet to
* the src pad now. First though we should check if we need to do
* descrambling */
if (G_UNLIKELY (stream->span > 1)) {
gst_asf_demux_descramble_buffer (demux, stream, &payload->buf);
}
payload->buf = gst_buffer_make_writable (payload->buf);
if (G_LIKELY (!payload->keyframe)) {
GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DELTA_UNIT);
}
if (G_UNLIKELY (stream->discont)) {
GST_DEBUG_OBJECT (stream->pad, "marking DISCONT on stream");
GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DISCONT);
stream->discont = FALSE;
}
if (G_UNLIKELY (stream->is_video && payload->par_x && payload->par_y &&
(payload->par_x != stream->par_x) &&
(payload->par_y != stream->par_y))) {
GST_DEBUG ("Updating PAR (%d/%d => %d/%d)",
stream->par_x, stream->par_y, payload->par_x, payload->par_y);
stream->par_x = payload->par_x;
stream->par_y = payload->par_y;
stream->caps = gst_caps_make_writable (stream->caps);
gst_caps_set_simple (stream->caps, "pixel-aspect-ratio",
GST_TYPE_FRACTION, stream->par_x, stream->par_y, NULL);
gst_pad_set_caps (stream->pad, stream->caps);
}
if (G_UNLIKELY (stream->interlaced != payload->interlaced)) {
GST_DEBUG ("Updating interlaced status (%d => %d)", stream->interlaced,
payload->interlaced);
stream->interlaced = payload->interlaced;
stream->caps = gst_caps_make_writable (stream->caps);
gst_caps_set_simple (stream->caps, "interlace-mode", G_TYPE_BOOLEAN,
(stream->interlaced ? "mixed" : "progressive"), NULL);
gst_pad_set_caps (stream->pad, stream->caps);
}
/* (sort of) interpolate timestamps using upstream "frame of reference",
* typically useful for live src, but might (unavoidably) mess with
* position reporting if a live src is playing not so live content
* (e.g. rtspsrc taking some time to fall back to tcp) */
timestamp = payload->ts;
if (GST_CLOCK_TIME_IS_VALID (timestamp)
&& !GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
timestamp += demux->in_gap;
/* Check if we're after the segment already, if so no need to push
* anything here */
if (demux->segment.stop != -1 && timestamp > demux->segment.stop) {
GST_DEBUG_OBJECT (stream->pad,
"Payload after segment stop %" GST_TIME_FORMAT,
GST_TIME_ARGS (demux->segment.stop));
ret =
gst_flow_combiner_update_pad_flow (demux->flowcombiner, stream->pad,
GST_FLOW_EOS);
gst_buffer_unref (payload->buf);
payload->buf = NULL;
g_array_remove_index (stream->payloads, 0);
/* Break out as soon as we have an issue */
if (G_UNLIKELY (ret != GST_FLOW_OK))
break;
continue;
}
}
GST_BUFFER_PTS (payload->buf) = timestamp;
if (payload->duration == GST_CLOCK_TIME_NONE
&& stream->ext_props.avg_time_per_frame != 0) {
duration = stream->ext_props.avg_time_per_frame * 100;
} else {
duration = payload->duration;
}
GST_BUFFER_DURATION (payload->buf) = duration;
/* FIXME: we should really set durations on buffers if we can */
GST_LOG_OBJECT (stream->pad, "pushing buffer, %" GST_PTR_FORMAT,
payload->buf);
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment) && stream->is_video) {
if (stream->reverse_kf_ready == TRUE && stream->kf_pos == 0) {
GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DISCONT);
}
} else if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)) {
GST_BUFFER_FLAG_SET (payload->buf, GST_BUFFER_FLAG_DISCONT);
}
if (stream->active) {
if (G_UNLIKELY (stream->first_buffer)) {
if (stream->streamheader != NULL) {
GST_DEBUG_OBJECT (stream->pad,
"Pushing streamheader before first buffer");
gst_pad_push (stream->pad, gst_buffer_ref (stream->streamheader));
}
stream->first_buffer = FALSE;
}
if (GST_CLOCK_TIME_IS_VALID (timestamp)
&& timestamp > demux->segment.position) {
demux->segment.position = timestamp;
if (GST_CLOCK_TIME_IS_VALID (duration))
demux->segment.position += timestamp;
}
ret = gst_pad_push (stream->pad, payload->buf);
ret =
gst_flow_combiner_update_pad_flow (demux->flowcombiner, stream->pad,
ret);
} else {
gst_buffer_unref (payload->buf);
ret = GST_FLOW_OK;
}
payload->buf = NULL;
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment) && stream->is_video
&& stream->reverse_kf_ready) {
g_array_remove_index (stream->payloads, stream->kf_pos);
stream->kf_pos--;
if (stream->reverse_kf_ready == TRUE && stream->kf_pos < 0) {
stream->kf_pos = 0;
stream->reverse_kf_ready = FALSE;
}
} else {
g_array_remove_index (stream->payloads, 0);
}
/* Break out as soon as we have an issue */
if (G_UNLIKELY (ret != GST_FLOW_OK))
break;
}
return ret;
}
static gboolean
gst_asf_demux_check_buffer_is_header (GstASFDemux * demux, GstBuffer * buf)
{
AsfObject obj;
GstMapInfo map;
gboolean valid;
g_assert (buf != NULL);
GST_LOG_OBJECT (demux, "Checking if buffer is a header");
gst_buffer_map (buf, &map, GST_MAP_READ);
/* we return false on buffer too small */
if (map.size < ASF_OBJECT_HEADER_SIZE) {
gst_buffer_unmap (buf, &map);
return FALSE;
}
/* check if it is a header */
valid =
asf_demux_peek_object (demux, map.data, ASF_OBJECT_HEADER_SIZE, &obj,
TRUE);
gst_buffer_unmap (buf, &map);
if (valid && obj.id == ASF_OBJ_HEADER) {
return TRUE;
}
return FALSE;
}
static gboolean
gst_asf_demux_check_chained_asf (GstASFDemux * demux)
{
guint64 off = demux->data_offset + (demux->packet * demux->packet_size);
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *buf = NULL;
gboolean header = FALSE;
/* TODO maybe we should skip index objects after the data and look
* further for a new header */
if (gst_asf_demux_pull_data (demux, off, ASF_OBJECT_HEADER_SIZE, &buf, &ret)) {
g_assert (buf != NULL);
/* check if it is a header */
if (gst_asf_demux_check_buffer_is_header (demux, buf)) {
GST_DEBUG_OBJECT (demux, "new base offset: %" G_GUINT64_FORMAT, off);
demux->base_offset = off;
header = TRUE;
}
gst_buffer_unref (buf);
}
return header;
}
static void
gst_asf_demux_loop (GstASFDemux * demux)
{
GstFlowReturn flow = GST_FLOW_OK;
GstBuffer *buf = NULL;
guint64 off;
if (G_UNLIKELY (demux->state == GST_ASF_DEMUX_STATE_HEADER)) {
if (!gst_asf_demux_pull_headers (demux, &flow)) {
goto pause;
}
flow = gst_asf_demux_pull_indices (demux);
if (flow != GST_FLOW_OK)
goto pause;
}
g_assert (demux->state == GST_ASF_DEMUX_STATE_DATA);
if (G_UNLIKELY (demux->num_packets != 0
&& demux->packet >= demux->num_packets))
goto eos;
GST_LOG_OBJECT (demux, "packet %u/%u", (guint) demux->packet + 1,
(guint) demux->num_packets);
off = demux->data_offset + (demux->packet * demux->packet_size);
if (G_UNLIKELY (!gst_asf_demux_pull_data (demux, off,
demux->packet_size * demux->speed_packets, &buf, &flow))) {
GST_DEBUG_OBJECT (demux, "got flow %s", gst_flow_get_name (flow));
if (flow == GST_FLOW_EOS) {
goto eos;
} else if (flow == GST_FLOW_FLUSHING) {
GST_DEBUG_OBJECT (demux, "Not fatal");
goto pause;
} else {
goto read_failed;
}
}
if (G_LIKELY (demux->speed_packets == 1)) {
GstAsfDemuxParsePacketError err;
err = gst_asf_demux_parse_packet (demux, buf);
if (G_UNLIKELY (err != GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE)) {
/* when we don't know when the data object ends, we should check
* for a chained asf */
if (demux->num_packets == 0) {
if (gst_asf_demux_check_buffer_is_header (demux, buf)) {
GST_INFO_OBJECT (demux, "Chained asf found");
demux->base_offset = off;
gst_asf_demux_reset (demux, TRUE);
gst_buffer_unref (buf);
return;
}
}
/* FIXME: We should tally up fatal errors and error out only
* after a few broken packets in a row? */
GST_INFO_OBJECT (demux, "Ignoring recoverable parse error");
gst_buffer_unref (buf);
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)
&& !demux->seek_to_cur_pos) {
--demux->packet;
if (demux->packet < 0) {
goto eos;
}
} else {
++demux->packet;
}
return;
}
flow = gst_asf_demux_push_complete_payloads (demux, FALSE);
if (GST_ASF_DEMUX_IS_REVERSE_PLAYBACK (demux->segment)
&& !demux->seek_to_cur_pos) {
--demux->packet;
if (demux->packet < 0) {
goto eos;
}
} else {
++demux->packet;
}
} else {
guint n;
for (n = 0; n < demux->speed_packets; n++) {
GstBuffer *sub;
GstAsfDemuxParsePacketError err;
sub =
gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL,
n * demux->packet_size, demux->packet_size);
err = gst_asf_demux_parse_packet (demux, sub);
if (G_UNLIKELY (err != GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE)) {
/* when we don't know when the data object ends, we should check
* for a chained asf */
if (demux->num_packets == 0) {
if (gst_asf_demux_check_buffer_is_header (demux, sub)) {
GST_INFO_OBJECT (demux, "Chained asf found");
demux->base_offset = off + n * demux->packet_size;
gst_asf_demux_reset (demux, TRUE);
gst_buffer_unref (sub);
gst_buffer_unref (buf);
return;
}
}
/* FIXME: We should tally up fatal errors and error out only
* after a few broken packets in a row? */
GST_INFO_OBJECT (demux, "Ignoring recoverable parse error");
flow = GST_FLOW_OK;
}
gst_buffer_unref (sub);
if (err == GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE)
flow = gst_asf_demux_push_complete_payloads (demux, FALSE);
++demux->packet;
}
/* reset speed pull */
demux->speed_packets = 1;
}
gst_buffer_unref (buf);
if (G_UNLIKELY ((demux->num_packets > 0
&& demux->packet >= demux->num_packets)
|| flow == GST_FLOW_EOS)) {
GST_LOG_OBJECT (demux, "reached EOS");
goto eos;
}
if (G_UNLIKELY (flow != GST_FLOW_OK)) {
GST_DEBUG_OBJECT (demux, "pushing complete payloads failed");
goto pause;
}
/* check if we're at the end of the configured segment */
/* FIXME: check if segment end reached etc. */
return;
eos:
{
/* if we haven't activated our streams yet, this might be because we have
* less data queued than required for preroll; force stream activation and
* send any pending payloads before sending EOS */
if (!demux->activated_streams)
flow = gst_asf_demux_push_complete_payloads (demux, TRUE);
/* we want to push an eos or post a segment-done in any case */
if (demux->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 = demux->segment.stop) == -1)
stop = demux->segment.duration;
GST_INFO_OBJECT (demux, "Posting segment-done, at end of segment");
gst_element_post_message (GST_ELEMENT_CAST (demux),
gst_message_new_segment_done (GST_OBJECT (demux), GST_FORMAT_TIME,
stop));
gst_asf_demux_send_event_unlocked (demux,
gst_event_new_segment_done (GST_FORMAT_TIME, stop));
} else if (flow != GST_FLOW_EOS) {
/* check if we have a chained asf, in case, we don't eos yet */
if (gst_asf_demux_check_chained_asf (demux)) {
GST_INFO_OBJECT (demux, "Chained ASF starting");
gst_asf_demux_reset (demux, TRUE);
return;
}
}
if (!(demux->segment.flags & GST_SEEK_FLAG_SEGMENT)) {
if (demux->activated_streams) {
/* normal playback, send EOS to all linked pads */
GST_INFO_OBJECT (demux, "Sending EOS, at end of stream");
gst_asf_demux_send_event_unlocked (demux, gst_event_new_eos ());
} else {
GST_WARNING_OBJECT (demux, "EOS without exposed streams");
flow = GST_FLOW_EOS;
}
}
/* ... and fall through to pause */
}
pause:
{
GST_DEBUG_OBJECT (demux, "pausing task, flow return: %s",
gst_flow_get_name (flow));
demux->segment_running = FALSE;
gst_pad_pause_task (demux->sinkpad);
/* For the error cases */
if (flow == GST_FLOW_EOS && !demux->activated_streams) {
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL),
("This doesn't seem to be an ASF file"));
} else if (flow < GST_FLOW_EOS || flow == GST_FLOW_NOT_LINKED) {
/* Post an error. Hopefully something else already has, but if not... */
GST_ELEMENT_FLOW_ERROR (demux, flow);
gst_asf_demux_send_event_unlocked (demux, gst_event_new_eos ());
}
return;
}
/* ERRORS */
read_failed:
{
GST_DEBUG_OBJECT (demux, "Read failed, doh");
flow = GST_FLOW_EOS;
goto pause;
}
#if 0
/* See FIXMEs above */
parse_error:
{
gst_buffer_unref (buf);
GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL),
("Error parsing ASF packet %u", (guint) demux->packet));
gst_asf_demux_send_event_unlocked (demux, gst_event_new_eos ());
flow = GST_FLOW_ERROR;
goto pause;
}
#endif
}
#define GST_ASF_DEMUX_CHECK_HEADER_YES 0
#define GST_ASF_DEMUX_CHECK_HEADER_NO 1
#define GST_ASF_DEMUX_CHECK_HEADER_NEED_DATA 2
static gint
gst_asf_demux_check_header (GstASFDemux * demux)
{
AsfObject obj;
guint8 *cdata = (guint8 *) gst_adapter_map (demux->adapter,
ASF_OBJECT_HEADER_SIZE);
if (cdata == NULL) /* need more data */
return GST_ASF_DEMUX_CHECK_HEADER_NEED_DATA;
if (asf_demux_peek_object (demux, cdata, ASF_OBJECT_HEADER_SIZE, &obj, FALSE)
&& obj.id == ASF_OBJ_HEADER) {
return GST_ASF_DEMUX_CHECK_HEADER_YES;
}
return GST_ASF_DEMUX_CHECK_HEADER_NO;
}
static GstFlowReturn
gst_asf_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
GstFlowReturn ret = GST_FLOW_OK;
GstASFDemux *demux;
demux = GST_ASF_DEMUX (parent);
GST_LOG_OBJECT (demux,
"buffer: size=%" G_GSIZE_FORMAT ", offset=%" G_GINT64_FORMAT ", time=%"
GST_TIME_FORMAT, gst_buffer_get_size (buf), GST_BUFFER_OFFSET (buf),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buf))) {
GST_DEBUG_OBJECT (demux, "received DISCONT");
gst_asf_demux_mark_discont (demux);
}
if (G_UNLIKELY ((!GST_CLOCK_TIME_IS_VALID (demux->in_gap) &&
GST_BUFFER_TIMESTAMP_IS_VALID (buf)))) {
demux->in_gap = GST_BUFFER_TIMESTAMP (buf) - demux->in_segment.start;
GST_DEBUG_OBJECT (demux, "upstream segment start %" GST_TIME_FORMAT
", interpolation gap: %" GST_TIME_FORMAT,
GST_TIME_ARGS (demux->in_segment.start), GST_TIME_ARGS (demux->in_gap));
}
gst_adapter_push (demux->adapter, buf);
switch (demux->state) {
case GST_ASF_DEMUX_STATE_INDEX:{
gint result = gst_asf_demux_check_header (demux);
if (result == GST_ASF_DEMUX_CHECK_HEADER_NEED_DATA) /* need more data */
break;
if (result == GST_ASF_DEMUX_CHECK_HEADER_NO) {
/* we don't care about this, probably an index */
/* TODO maybe would be smarter to skip all the indices
* until we got a new header or EOS to decide */
GST_LOG_OBJECT (demux, "Received index object, its EOS");
goto eos;
} else {
GST_INFO_OBJECT (demux, "Chained asf starting");
/* cleanup and get ready for a chained asf */
gst_asf_demux_reset (demux, TRUE);
/* fall through */
}
}
case GST_ASF_DEMUX_STATE_HEADER:{
ret = gst_asf_demux_chain_headers (demux);
if (demux->state != GST_ASF_DEMUX_STATE_DATA)
break;
/* otherwise fall through */
}
case GST_ASF_DEMUX_STATE_DATA:
{
guint64 data_size;
data_size = demux->packet_size;
while (gst_adapter_available (demux->adapter) >= data_size) {
GstBuffer *buf;
GstAsfDemuxParsePacketError err;
/* we don't know the length of the stream
* check for a chained asf everytime */
if (demux->num_packets == 0) {
gint result = gst_asf_demux_check_header (demux);
if (result == GST_ASF_DEMUX_CHECK_HEADER_YES) {
GST_INFO_OBJECT (demux, "Chained asf starting");
/* cleanup and get ready for a chained asf */
gst_asf_demux_reset (demux, TRUE);
break;
}
} else if (G_UNLIKELY (demux->num_packets != 0 && demux->packet >= 0
&& demux->packet >= demux->num_packets)) {
/* do not overshoot data section when streaming */
break;
}
buf = gst_adapter_take_buffer (demux->adapter, data_size);
/* FIXME: We should tally up fatal errors and error out only
* after a few broken packets in a row? */
err = gst_asf_demux_parse_packet (demux, buf);
gst_buffer_unref (buf);
if (G_LIKELY (err == GST_ASF_DEMUX_PARSE_PACKET_ERROR_NONE))
ret = gst_asf_demux_push_complete_payloads (demux, FALSE);
else
GST_WARNING_OBJECT (demux, "Parse error");
if (demux->packet >= 0)
++demux->packet;
}
if (G_UNLIKELY (demux->num_packets != 0 && demux->packet >= 0
&& demux->packet >= demux->num_packets)) {
demux->state = GST_ASF_DEMUX_STATE_INDEX;
}
break;
}
default:
g_assert_not_reached ();
}
done:
if (ret != GST_FLOW_OK)
GST_DEBUG_OBJECT (demux, "flow: %s", gst_flow_get_name (ret));
return ret;
eos:
{
GST_DEBUG_OBJECT (demux, "Handled last packet, setting EOS");
ret = GST_FLOW_EOS;
goto done;
}
}
static inline gboolean
gst_asf_demux_skip_bytes (guint num_bytes, guint8 ** p_data, guint64 * p_size)
{
if (*p_size < num_bytes)
return FALSE;
*p_data += num_bytes;
*p_size -= num_bytes;
return TRUE;
}
static inline guint8
gst_asf_demux_get_uint8 (guint8 ** p_data, guint64 * p_size)
{
guint8 ret;
g_assert (*p_size >= 1);
ret = GST_READ_UINT8 (*p_data);
*p_data += sizeof (guint8);
*p_size -= sizeof (guint8);
return ret;
}
static inline guint16
gst_asf_demux_get_uint16 (guint8 ** p_data, guint64 * p_size)
{
guint16 ret;
g_assert (*p_size >= 2);
ret = GST_READ_UINT16_LE (*p_data);
*p_data += sizeof (guint16);
*p_size -= sizeof (guint16);
return ret;
}
static inline guint32
gst_asf_demux_get_uint32 (guint8 ** p_data, guint64 * p_size)
{
guint32 ret;
g_assert (*p_size >= 4);
ret = GST_READ_UINT32_LE (*p_data);
*p_data += sizeof (guint32);
*p_size -= sizeof (guint32);
return ret;
}
static inline guint64
gst_asf_demux_get_uint64 (guint8 ** p_data, guint64 * p_size)
{
guint64 ret;
g_assert (*p_size >= 8);
ret = GST_READ_UINT64_LE (*p_data);
*p_data += sizeof (guint64);
*p_size -= sizeof (guint64);
return ret;
}
static gboolean
gst_asf_demux_get_buffer (GstBuffer ** p_buf, guint num_bytes_to_read,
guint8 ** p_data, guint64 * p_size)
{
*p_buf = NULL;
if (*p_size < num_bytes_to_read)
return FALSE;
*p_buf = gst_buffer_new_and_alloc (num_bytes_to_read);
gst_buffer_fill (*p_buf, 0, *p_data, num_bytes_to_read);
*p_data += num_bytes_to_read;
*p_size -= num_bytes_to_read;
return TRUE;
}
static gboolean
gst_asf_demux_get_bytes (guint8 ** p_buf, guint num_bytes_to_read,
guint8 ** p_data, guint64 * p_size)
{
*p_buf = NULL;
if (*p_size < num_bytes_to_read)
return FALSE;
*p_buf = g_memdup (*p_data, num_bytes_to_read);
*p_data += num_bytes_to_read;
*p_size -= num_bytes_to_read;
return TRUE;
}
static gboolean
gst_asf_demux_get_string (gchar ** p_str, guint16 * p_strlen,
guint8 ** p_data, guint64 * p_size)
{
guint16 s_length;
guint8 *s;
*p_str = NULL;
if (*p_size < 2)
return FALSE;
s_length = gst_asf_demux_get_uint16 (p_data, p_size);
if (p_strlen)
*p_strlen = s_length;
if (s_length == 0) {
GST_WARNING ("zero-length string");
*p_str = g_strdup ("");
return TRUE;
}
if (!gst_asf_demux_get_bytes (&s, s_length, p_data, p_size))
return FALSE;
g_assert (s != NULL);
/* just because They don't exist doesn't
* mean They are not out to get you ... */
if (s[s_length - 1] != '\0') {
s = g_realloc (s, s_length + 1);
s[s_length] = '\0';
}
*p_str = (gchar *) s;
return TRUE;
}
static void
gst_asf_demux_get_guid (ASFGuid * guid, guint8 ** p_data, guint64 * p_size)
{
g_assert (*p_size >= 4 * sizeof (guint32));
guid->v1 = gst_asf_demux_get_uint32 (p_data, p_size);
guid->v2 = gst_asf_demux_get_uint32 (p_data, p_size);
guid->v3 = gst_asf_demux_get_uint32 (p_data, p_size);
guid->v4 = gst_asf_demux_get_uint32 (p_data, p_size);
}
static gboolean
gst_asf_demux_get_stream_audio (asf_stream_audio * audio, guint8 ** p_data,
guint64 * p_size)
{
if (*p_size < (2 + 2 + 4 + 4 + 2 + 2 + 2))
return FALSE;
/* WAVEFORMATEX Structure */
audio->codec_tag = gst_asf_demux_get_uint16 (p_data, p_size);
audio->channels = gst_asf_demux_get_uint16 (p_data, p_size);
audio->sample_rate = gst_asf_demux_get_uint32 (p_data, p_size);
audio->byte_rate = gst_asf_demux_get_uint32 (p_data, p_size);
audio->block_align = gst_asf_demux_get_uint16 (p_data, p_size);
audio->word_size = gst_asf_demux_get_uint16 (p_data, p_size);
/* Codec specific data size */
audio->size = gst_asf_demux_get_uint16 (p_data, p_size);
if (audio->size > *p_size) {
GST_WARNING ("Corrupted audio codec_data (should be at least %u bytes, is %"
G_GUINT64_FORMAT " long)", audio->size, *p_size);
return FALSE;
}
return TRUE;
}
static gboolean
gst_asf_demux_get_stream_video (asf_stream_video * video, guint8 ** p_data,
guint64 * p_size)
{
if (*p_size < (4 + 4 + 1 + 2))
return FALSE;
video->width = gst_asf_demux_get_uint32 (p_data, p_size);
video->height = gst_asf_demux_get_uint32 (p_data, p_size);
video->unknown = gst_asf_demux_get_uint8 (p_data, p_size);
video->size = gst_asf_demux_get_uint16 (p_data, p_size);
return TRUE;
}
static gboolean
gst_asf_demux_get_stream_video_format (asf_stream_video_format * fmt,
guint8 ** p_data, guint64 * p_size)
{
if (*p_size < (4 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 4))
return FALSE;
fmt->size = gst_asf_demux_get_uint32 (p_data, p_size);
/* Sanity checks */
if (fmt->size < 40) {
GST_WARNING ("Corrupted asf_stream_video_format (size < 40)");
return FALSE;
}
if ((guint64) fmt->size - 4 > *p_size) {
GST_WARNING ("Corrupted asf_stream_video_format (codec_data is too small)");
return FALSE;
}
fmt->width = gst_asf_demux_get_uint32 (p_data, p_size);
fmt->height = gst_asf_demux_get_uint32 (p_data, p_size);
fmt->planes = gst_asf_demux_get_uint16 (p_data, p_size);
fmt->depth = gst_asf_demux_get_uint16 (p_data, p_size);
fmt->tag = gst_asf_demux_get_uint32 (p_data, p_size);
fmt->image_size = gst_asf_demux_get_uint32 (p_data, p_size);
fmt->xpels_meter = gst_asf_demux_get_uint32 (p_data, p_size);
fmt->ypels_meter = gst_asf_demux_get_uint32 (p_data, p_size);
fmt->num_colors = gst_asf_demux_get_uint32 (p_data, p_size);
fmt->imp_colors = gst_asf_demux_get_uint32 (p_data, p_size);
return TRUE;
}
AsfStream *
gst_asf_demux_get_stream (GstASFDemux * demux, guint16 id)
{
guint i;
for (i = 0; i < demux->num_streams; i++) {
if (demux->stream[i].id == id)
return &demux->stream[i];
}
if (gst_asf_demux_is_unknown_stream (demux, id))
GST_WARNING ("Segment found for undefined stream: (%d)", id);
return NULL;
}
static AsfStream *
gst_asf_demux_setup_pad (GstASFDemux * demux, GstPad * src_pad,
GstCaps * caps, guint16 id, gboolean is_video, GstBuffer * streamheader,
GstTagList * tags)
{
AsfStream *stream;
gst_pad_use_fixed_caps (src_pad);
gst_pad_set_caps (src_pad, caps);
gst_pad_set_event_function (src_pad,
GST_DEBUG_FUNCPTR (gst_asf_demux_handle_src_event));
gst_pad_set_query_function (src_pad,
GST_DEBUG_FUNCPTR (gst_asf_demux_handle_src_query));
stream = &demux->stream[demux->num_streams];
stream->caps = caps;
stream->pad = src_pad;
stream->id = id;
stream->fps_known = !is_video; /* bit hacky for audio */
stream->is_video = is_video;
stream->pending_tags = tags;
stream->discont = TRUE;
stream->first_buffer = TRUE;
stream->streamheader = streamheader;
if (stream->streamheader) {
stream->streamheader = gst_buffer_make_writable (streamheader);
GST_BUFFER_FLAG_SET (stream->streamheader, GST_BUFFER_FLAG_HEADER);
}
if (is_video) {
GstStructure *st;
gint par_x, par_y;
st = gst_caps_get_structure (caps, 0);
if (gst_structure_get_fraction (st, "pixel-aspect-ratio", &par_x, &par_y) &&
par_x > 0 && par_y > 0) {
GST_DEBUG ("PAR %d/%d", par_x, par_y);
stream->par_x = par_x;
stream->par_y = par_y;
}
}
stream->payloads = g_array_new (FALSE, FALSE, sizeof (AsfPayload));
/* TODO: create this array during reverse play? */
stream->payloads_rev = g_array_new (FALSE, FALSE, sizeof (AsfPayload));
GST_INFO ("Created pad %s for stream %u with caps %" GST_PTR_FORMAT,
GST_PAD_NAME (src_pad), demux->num_streams, caps);
++demux->num_streams;
stream->active = FALSE;
return stream;
}
static void
gst_asf_demux_add_stream_headers_to_caps (GstASFDemux * demux,
GstBuffer * buffer, GstStructure * structure)
{
GValue arr_val = G_VALUE_INIT;
GValue buf_val = G_VALUE_INIT;
g_value_init (&arr_val, GST_TYPE_ARRAY);
g_value_init (&buf_val, GST_TYPE_BUFFER);
gst_value_set_buffer (&buf_val, buffer);
gst_value_array_append_and_take_value (&arr_val, &buf_val);
gst_structure_take_value (structure, "streamheader", &arr_val);
}
static AsfStream *
gst_asf_demux_add_audio_stream (GstASFDemux * demux,
asf_stream_audio * audio, guint16 id, guint8 ** p_data, guint64 * p_size)
{
GstTagList *tags = NULL;
GstBuffer *extradata = NULL;
GstPad *src_pad;
GstCaps *caps;
guint16 size_left = 0;
gchar *codec_name = NULL;
gchar *name = NULL;
size_left = audio->size;
/* Create the audio pad */
name = g_strdup_printf ("audio_%u", demux->num_audio_streams);
src_pad = gst_pad_new_from_static_template (&audio_src_template, name);
g_free (name);