/* * This library is licensed under 2 different licenses and you * can choose to use it under the terms of either one of them. The * two licenses are the MPL 1.1 and the LGPL. * * MPL: * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * LGPL: * * 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. * * The Original Code is Fluendo MPEG Demuxer plugin. * * The Initial Developer of the Original Code is Fluendo, S.L. * Portions created by Fluendo, S.L. are Copyright (C) 2005 * Fluendo, S.L. All Rights Reserved. * * Contributor(s): Wim Taymans * Jan Schmidt */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "gstmpegdefs.h" #include "gstmpegdemux.h" #define BLOCK_SZ 32768 #define SCAN_SCR_SZ 12 #define SCAN_PTS_SZ 80 #define SEGMENT_THRESHOLD (300*GST_MSECOND) #define VIDEO_SEGMENT_THRESHOLD (500*GST_MSECOND) #define DURATION_SCAN_LIMIT 4 * 1024 * 1024 typedef enum { SCAN_SCR, SCAN_DTS, SCAN_PTS } SCAN_MODE; /* We clamp scr delta with 0 so negative bytes won't be possible */ #define GSTTIME_TO_BYTES(time) \ ((time != -1) ? gst_util_uint64_scale (MAX(0,(gint64) (GSTTIME_TO_MPEGTIME(time))), demux->scr_rate_n, demux->scr_rate_d) : -1) #define BYTES_TO_GSTTIME(bytes) ((bytes != -1) ? MPEGTIME_TO_GSTTIME(gst_util_uint64_scale (bytes, demux->scr_rate_d, demux->scr_rate_n)) : -1) #define ADAPTER_OFFSET_FLUSH(_bytes_) demux->adapter_offset += (_bytes_) GST_DEBUG_CATEGORY_STATIC (gstflupsdemux_debug); #define GST_CAT_DEFAULT (gstflupsdemux_debug) /* MPEG2Demux signals and args */ enum { /* FILL ME */ LAST_SIGNAL }; enum { ARG_0, ARG_SYNC, /* FILL ME */ }; static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/mpeg, " "mpegversion = (int) { 1, 2 }, " "systemstream = (boolean) TRUE;" "video/x-cdxa") ); static GstStaticPadTemplate video_template = GST_STATIC_PAD_TEMPLATE ("video_%02x", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS ("video/mpeg, " "mpegversion = (int) { 1, 2, 4 }, " "systemstream = (boolean) FALSE, " "parsed = (boolean) FALSE; " "video/x-h264") ); static GstStaticPadTemplate audio_template = GST_STATIC_PAD_TEMPLATE ("audio_%02x", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS ("audio/mpeg, mpegversion = (int) 1;" "audio/mpeg, mpegversion = (int) 4, stream-format = (string) { adts, loas };" "audio/x-private1-lpcm; " "audio/x-private1-ac3;" "audio/x-private1-dts;" "audio/ac3") ); static GstStaticPadTemplate subpicture_template = GST_STATIC_PAD_TEMPLATE ("subpicture_%02x", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS ("subpicture/x-dvd") ); static GstStaticPadTemplate private_template = GST_STATIC_PAD_TEMPLATE ("private_%d", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); static void gst_ps_demux_base_init (GstPsDemuxClass * klass); static void gst_ps_demux_class_init (GstPsDemuxClass * klass); static void gst_ps_demux_init (GstPsDemux * demux); static void gst_ps_demux_finalize (GstPsDemux * demux); static void gst_ps_demux_reset (GstPsDemux * demux); static gboolean gst_ps_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event); static GstFlowReturn gst_ps_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer); static gboolean gst_ps_demux_sink_activate (GstPad * sinkpad, GstObject * parent); static gboolean gst_ps_demux_sink_activate_mode (GstPad * pad, GstObject * parent, GstPadMode mode, gboolean active); static void gst_ps_demux_loop (GstPad * pad); static gboolean gst_ps_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_ps_demux_src_query (GstPad * pad, GstObject * parent, GstQuery * query); static GstStateChangeReturn gst_ps_demux_change_state (GstElement * element, GstStateChange transition); static inline gboolean gst_ps_demux_scan_forward_ts (GstPsDemux * demux, guint64 * pos, SCAN_MODE mode, guint64 * rts, gint limit); static inline gboolean gst_ps_demux_scan_backward_ts (GstPsDemux * demux, guint64 * pos, SCAN_MODE mode, guint64 * rts, gint limit); static inline void gst_ps_demux_send_gap_updates (GstPsDemux * demux, GstClockTime new_time); static inline void gst_ps_demux_clear_times (GstPsDemux * demux); static void gst_ps_demux_reset_psm (GstPsDemux * demux); static void gst_ps_demux_flush (GstPsDemux * demux); static GstElementClass *parent_class = NULL; static void gst_segment_set_position (GstSegment * segment, GstFormat format, guint64 position); static void gst_segment_set_duration (GstSegment * segment, GstFormat format, guint64 duration); /*static guint gst_ps_demux_signals[LAST_SIGNAL] = { 0 };*/ GType gst_ps_demux_get_type (void) { static GType ps_demux_type = 0; if (!ps_demux_type) { static const GTypeInfo ps_demux_info = { sizeof (GstPsDemuxClass), (GBaseInitFunc) gst_ps_demux_base_init, NULL, (GClassInitFunc) gst_ps_demux_class_init, NULL, NULL, sizeof (GstPsDemux), 0, (GInstanceInitFunc) gst_ps_demux_init, NULL }; ps_demux_type = g_type_register_static (GST_TYPE_ELEMENT, "GstMpegPSDemux", &ps_demux_info, 0); GST_DEBUG_CATEGORY_INIT (gstflupsdemux_debug, "mpegpsdemux", 0, "MPEG program stream demultiplexer element"); } return ps_demux_type; } static void gst_ps_demux_base_init (GstPsDemuxClass * klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); klass->sink_template = gst_static_pad_template_get (&sink_template); klass->video_template = gst_static_pad_template_get (&video_template); klass->audio_template = gst_static_pad_template_get (&audio_template); klass->subpicture_template = gst_static_pad_template_get (&subpicture_template); klass->private_template = gst_static_pad_template_get (&private_template); gst_element_class_add_pad_template (element_class, klass->video_template); gst_element_class_add_pad_template (element_class, klass->audio_template); gst_element_class_add_pad_template (element_class, klass->subpicture_template); gst_element_class_add_pad_template (element_class, klass->private_template); gst_element_class_add_pad_template (element_class, klass->sink_template); gst_element_class_set_static_metadata (element_class, "The Fluendo MPEG Program Stream Demuxer", "Codec/Demuxer", "Demultiplexes MPEG Program Streams", "Wim Taymans "); } static void gst_ps_demux_class_init (GstPsDemuxClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; parent_class = g_type_class_ref (GST_TYPE_ELEMENT); gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gobject_class->finalize = (GObjectFinalizeFunc) gst_ps_demux_finalize; gstelement_class->change_state = gst_ps_demux_change_state; } static void gst_ps_demux_init (GstPsDemux * demux) { GstPsDemuxClass *klass = GST_PS_DEMUX_GET_CLASS (demux); demux->sinkpad = gst_pad_new_from_template (klass->sink_template, "sink"); gst_pad_set_event_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_ps_demux_sink_event)); gst_pad_set_chain_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_ps_demux_chain)); gst_pad_set_activate_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_ps_demux_sink_activate)); gst_pad_set_activatemode_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_ps_demux_sink_activate_mode)); gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); demux->streams = g_malloc0 (sizeof (GstPsStream *) * (GST_PS_DEMUX_MAX_STREAMS)); demux->streams_found = g_malloc0 (sizeof (GstPsStream *) * (GST_PS_DEMUX_MAX_STREAMS)); demux->found_count = 0; demux->adapter = gst_adapter_new (); demux->rev_adapter = gst_adapter_new (); demux->flowcombiner = gst_flow_combiner_new (); gst_ps_demux_reset (demux); } static void gst_ps_demux_finalize (GstPsDemux * demux) { gst_ps_demux_reset (demux); g_free (demux->streams); g_free (demux->streams_found); gst_flow_combiner_free (demux->flowcombiner); g_object_unref (demux->adapter); g_object_unref (demux->rev_adapter); G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT (demux)); } static void gst_ps_demux_reset (GstPsDemux * demux) { /* Clean up the streams and pads we allocated */ gint i; for (i = 0; i < GST_PS_DEMUX_MAX_STREAMS; i++) { GstPsStream *stream = demux->streams[i]; if (stream != NULL) { if (stream->pad && GST_PAD_PARENT (stream->pad)) { gst_flow_combiner_remove_pad (demux->flowcombiner, stream->pad); gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad); } else { gst_object_unref (stream->pad); } if (stream->pending_tags) gst_tag_list_unref (stream->pending_tags); g_free (stream); demux->streams[i] = NULL; } } memset (demux->streams_found, 0, sizeof (GstPsStream *) * (GST_PS_DEMUX_MAX_STREAMS)); demux->found_count = 0; gst_adapter_clear (demux->adapter); gst_adapter_clear (demux->rev_adapter); demux->adapter_offset = G_MAXUINT64; demux->first_scr = G_MAXUINT64; demux->last_scr = G_MAXUINT64; demux->current_scr = G_MAXUINT64; demux->base_time = G_MAXUINT64; demux->scr_rate_n = G_MAXUINT64; demux->scr_rate_d = G_MAXUINT64; demux->first_pts = G_MAXUINT64; demux->last_pts = G_MAXUINT64; demux->mux_rate = G_MAXUINT64; demux->next_pts = G_MAXUINT64; demux->next_dts = G_MAXUINT64; demux->need_no_more_pads = TRUE; demux->adjust_segment = TRUE; gst_ps_demux_reset_psm (demux); gst_segment_init (&demux->sink_segment, GST_FORMAT_UNDEFINED); gst_segment_init (&demux->src_segment, GST_FORMAT_TIME); gst_ps_demux_flush (demux); demux->have_group_id = FALSE; demux->group_id = G_MAXUINT; } static GstPsStream * gst_ps_demux_create_stream (GstPsDemux * demux, gint id, gint stream_type) { GstPsStream *stream; GstPadTemplate *template; gchar *name; GstPsDemuxClass *klass = GST_PS_DEMUX_GET_CLASS (demux); GstCaps *caps; GstClockTime threshold = SEGMENT_THRESHOLD; GstEvent *event; gchar *stream_id; name = NULL; template = NULL; caps = NULL; GST_DEBUG_OBJECT (demux, "create stream id 0x%02x, type 0x%02x", id, stream_type); switch (stream_type) { case ST_VIDEO_MPEG1: case ST_VIDEO_MPEG2: case ST_VIDEO_MPEG4: case ST_GST_VIDEO_MPEG1_OR_2: { gint mpeg_version = 1; if (stream_type == ST_VIDEO_MPEG2 || (stream_type == ST_GST_VIDEO_MPEG1_OR_2 && demux->is_mpeg2_pack)) { mpeg_version = 2; } if (stream_type == ST_VIDEO_MPEG4) { mpeg_version = 4; } template = klass->video_template; name = g_strdup_printf ("video_%02x", id); caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, mpeg_version, "systemstream", G_TYPE_BOOLEAN, FALSE, "parsed", G_TYPE_BOOLEAN, FALSE, NULL); threshold = VIDEO_SEGMENT_THRESHOLD; break; } case ST_AUDIO_MPEG1: case ST_AUDIO_MPEG2: template = klass->audio_template; name = g_strdup_printf ("audio_%02x", id); caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 1, NULL); break; case ST_PRIVATE_SECTIONS: case ST_PRIVATE_DATA: case ST_MHEG: case ST_DSMCC: break; case ST_AUDIO_AAC_ADTS: template = klass->audio_template; name = g_strdup_printf ("audio_%02x", id); caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 4, "stream-format", G_TYPE_STRING, "adts", NULL); break; case ST_AUDIO_AAC_LOAS: // LATM/LOAS AAC syntax template = klass->audio_template; name = g_strdup_printf ("audio_%02x", id); caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 4, "stream-format", G_TYPE_STRING, "loas", NULL); break; case ST_VIDEO_H264: template = klass->video_template; name = g_strdup_printf ("video_%02x", id); caps = gst_caps_new_empty_simple ("video/x-h264"); threshold = VIDEO_SEGMENT_THRESHOLD; break; case ST_PS_AUDIO_AC3: template = klass->audio_template; name = g_strdup_printf ("audio_%02x", id); caps = gst_caps_new_empty_simple ("audio/x-private1-ac3"); break; case ST_PS_AUDIO_DTS: template = klass->audio_template; name = g_strdup_printf ("audio_%02x", id); caps = gst_caps_new_empty_simple ("audio/x-private1-dts"); break; case ST_PS_AUDIO_LPCM: template = klass->audio_template; name = g_strdup_printf ("audio_%02x", id); caps = gst_caps_new_empty_simple ("audio/x-private1-lpcm"); break; case ST_PS_DVD_SUBPICTURE: template = klass->subpicture_template; name = g_strdup_printf ("subpicture_%02x", id); caps = gst_caps_new_empty_simple ("subpicture/x-dvd"); break; case ST_GST_AUDIO_RAWA52: template = klass->audio_template; name = g_strdup_printf ("audio_%02x", id); caps = gst_caps_new_empty_simple ("audio/ac3"); break; default: break; } if (name == NULL || template == NULL || caps == NULL) { g_free (name); if (caps) gst_caps_unref (caps); return FALSE; } stream = g_new0 (GstPsStream, 1); stream->id = id; stream->discont = TRUE; stream->need_segment = TRUE; stream->notlinked = FALSE; stream->type = stream_type; stream->pending_tags = NULL; stream->pad = gst_pad_new_from_template (template, name); stream->segment_thresh = threshold; gst_pad_set_event_function (stream->pad, GST_DEBUG_FUNCPTR (gst_ps_demux_src_event)); gst_pad_set_query_function (stream->pad, GST_DEBUG_FUNCPTR (gst_ps_demux_src_query)); gst_pad_use_fixed_caps (stream->pad); /* needed for set_caps to work */ if (!gst_pad_set_active (stream->pad, TRUE)) { GST_WARNING_OBJECT (demux, "Failed to activate pad %" GST_PTR_FORMAT, stream->pad); } stream_id = gst_pad_create_stream_id_printf (stream->pad, GST_ELEMENT_CAST (demux), "%02x", id); event = gst_pad_get_sticky_event (demux->sinkpad, GST_EVENT_STREAM_START, 0); if (event) { if (gst_event_parse_group_id (event, &demux->group_id)) demux->have_group_id = TRUE; else demux->have_group_id = FALSE; gst_event_unref (event); } else if (!demux->have_group_id) { demux->have_group_id = TRUE; demux->group_id = gst_util_group_id_next (); } event = gst_event_new_stream_start (stream_id); if (demux->have_group_id) gst_event_set_group_id (event, demux->group_id); gst_pad_push_event (stream->pad, event); g_free (stream_id); gst_pad_set_caps (stream->pad, caps); if (!stream->pending_tags) stream->pending_tags = gst_tag_list_new_empty (); gst_pb_utils_add_codec_description_to_tag_list (stream->pending_tags, NULL, caps); GST_DEBUG_OBJECT (demux, "create pad %s, caps %" GST_PTR_FORMAT, name, caps); gst_caps_unref (caps); g_free (name); return stream; } static GstPsStream * gst_ps_demux_get_stream (GstPsDemux * demux, gint id, gint type) { GstPsStream *stream = demux->streams[id]; if (stream == NULL) { if (!(stream = gst_ps_demux_create_stream (demux, id, type))) goto unknown_stream; GST_DEBUG_OBJECT (demux, "adding pad for stream id 0x%02x type 0x%02x", id, type); if (demux->need_no_more_pads) { gst_element_add_pad (GST_ELEMENT (demux), stream->pad); gst_flow_combiner_add_pad (demux->flowcombiner, stream->pad); } else { /* only likely to confuse decodebin etc, so discard */ /* FIXME should perform full switch protocol: * add a whole new set of pads, drop old and no-more-pads again */ GST_DEBUG_OBJECT (demux, "but already signalled no-more-pads; not adding"); gst_object_ref_sink (stream->pad); } demux->streams[id] = stream; demux->streams_found[demux->found_count++] = stream; } return stream; /* ERROR */ unknown_stream: { GST_DEBUG_OBJECT (demux, "unknown stream id 0x%02x type 0x%02x", id, type); return NULL; } } static GstPsStream * gst_ps_demux_get_stream_from_pad (GstPsDemux * demux, GstPad * srcpad) { gint i, count; count = demux->found_count; for (i = 0; i < count; i++) { GstPsStream *stream = demux->streams_found[i]; if (stream && stream->pad == srcpad) return stream; } GST_DEBUG_OBJECT (srcpad, "no stream found for pad!"); return NULL; } static inline void gst_ps_demux_send_segment (GstPsDemux * demux, GstPsStream * stream, GstClockTime pts) { /* discont */ if (G_UNLIKELY (stream->need_segment)) { GstSegment segment; GST_DEBUG ("PTS timestamp:%" GST_TIME_FORMAT " base_time %" GST_TIME_FORMAT " src_segment.start:%" GST_TIME_FORMAT " .stop:%" GST_TIME_FORMAT, GST_TIME_ARGS (pts), GST_TIME_ARGS (demux->base_time), GST_TIME_ARGS (demux->src_segment.start), GST_TIME_ARGS (demux->src_segment.stop)); /* adjust segment start if estimating a seek was off quite a bit, * make sure to do for all streams though to preserve a/v sync */ /* FIXME such adjustment tends to be frowned upon */ if (pts != GST_CLOCK_TIME_NONE && demux->adjust_segment) { if (demux->src_segment.rate > 0) { if (GST_CLOCK_DIFF (demux->src_segment.start, pts) > GST_SECOND) demux->src_segment.start = pts - demux->base_time; } else { if (GST_CLOCK_DIFF (demux->src_segment.stop, pts) > GST_SECOND) demux->src_segment.stop = pts - demux->base_time; } } demux->adjust_segment = FALSE; /* we should be in sync with downstream, so start from our segment notion, * which also includes proper base_time etc, tweak it a bit and send */ gst_segment_copy_into (&demux->src_segment, &segment); if (GST_CLOCK_TIME_IS_VALID (demux->base_time)) { if (GST_CLOCK_TIME_IS_VALID (segment.start)) segment.start += demux->base_time; if (GST_CLOCK_TIME_IS_VALID (segment.stop)) segment.stop += demux->base_time; segment.time = segment.start - demux->base_time; } GST_INFO_OBJECT (demux, "sending segment event %" GST_SEGMENT_FORMAT " to pad %" GST_PTR_FORMAT, &segment, stream->pad); gst_pad_push_event (stream->pad, gst_event_new_segment (&segment)); stream->need_segment = FALSE; } if (G_UNLIKELY (stream->pending_tags)) { GST_DEBUG_OBJECT (demux, "Sending pending_tags %p for pad %s:%s : %" GST_PTR_FORMAT, stream->pending_tags, GST_DEBUG_PAD_NAME (stream->pad), stream->pending_tags); gst_pad_push_event (stream->pad, gst_event_new_tag (stream->pending_tags)); stream->pending_tags = NULL; } } static GstFlowReturn gst_ps_demux_send_data (GstPsDemux * demux, GstPsStream * stream, GstBuffer * buf) { GstFlowReturn result; GstClockTime pts = GST_CLOCK_TIME_NONE, dts = GST_CLOCK_TIME_NONE; if (stream == NULL) goto no_stream; /* timestamps */ if (G_UNLIKELY (demux->next_pts != G_MAXUINT64)) pts = MPEGTIME_TO_GSTTIME (demux->next_pts); if (G_UNLIKELY (demux->next_dts != G_MAXUINT64)) dts = MPEGTIME_TO_GSTTIME (demux->next_dts); gst_ps_demux_send_segment (demux, stream, pts); /* OK, sent new segment now prepare the buffer for sending */ GST_BUFFER_PTS (buf) = pts; GST_BUFFER_DTS (buf) = dts; /* update position in the segment */ gst_segment_set_position (&demux->src_segment, GST_FORMAT_TIME, MPEGTIME_TO_GSTTIME (demux->current_scr - demux->first_scr)); GST_LOG_OBJECT (demux, "last stop position is now %" GST_TIME_FORMAT " current scr is %" GST_TIME_FORMAT, GST_TIME_ARGS (demux->src_segment.position), GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->current_scr))); if (demux->src_segment.position != GST_CLOCK_TIME_NONE && demux->base_time != GST_CLOCK_TIME_NONE) { GstClockTime new_time = demux->base_time + demux->src_segment.position; if (stream->last_ts == GST_CLOCK_TIME_NONE || stream->last_ts < new_time) { GST_LOG_OBJECT (demux, "last_ts update on pad %s to time %" GST_TIME_FORMAT, GST_PAD_NAME (stream->pad), GST_TIME_ARGS (new_time)); stream->last_ts = new_time; } gst_ps_demux_send_gap_updates (demux, new_time); } /* Set the buffer discont flag, and clear discont state on the stream */ if (stream->discont) { GST_DEBUG_OBJECT (demux, "discont buffer to pad %" GST_PTR_FORMAT " with PTS %" GST_TIME_FORMAT " DTS %" GST_TIME_FORMAT, stream->pad, GST_TIME_ARGS (pts), GST_TIME_ARGS (dts)); GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); stream->discont = FALSE; } else { GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); } demux->next_pts = G_MAXUINT64; demux->next_dts = G_MAXUINT64; GST_LOG_OBJECT (demux, "pushing stream id 0x%02x type 0x%02x, pts time: %" GST_TIME_FORMAT ", size %" G_GSIZE_FORMAT, stream->id, stream->type, GST_TIME_ARGS (pts), gst_buffer_get_size (buf)); result = gst_pad_push (stream->pad, buf); GST_LOG_OBJECT (demux, "result: %s", gst_flow_get_name (result)); return result; /* ERROR */ no_stream: { GST_DEBUG_OBJECT (demux, "no stream given"); gst_buffer_unref (buf); return GST_FLOW_OK; } } static inline void gst_ps_demux_mark_discont (GstPsDemux * demux, gboolean discont, gboolean need_segment) { gint i, count = demux->found_count; /* mark discont on all streams */ for (i = 0; i < count; i++) { GstPsStream *stream = demux->streams_found[i]; if (G_LIKELY (stream)) { stream->discont |= discont; stream->need_segment |= need_segment; demux->adjust_segment |= need_segment; GST_DEBUG_OBJECT (demux, "marked stream as discont %d, need_segment %d", stream->discont, stream->need_segment); } } } static gboolean gst_ps_demux_send_event (GstPsDemux * demux, GstEvent * event) { gint i, count = demux->found_count; gboolean ret = FALSE; for (i = 0; i < count; i++) { GstPsStream *stream = demux->streams_found[i]; if (stream) { if (!gst_pad_push_event (stream->pad, gst_event_ref (event))) { GST_DEBUG_OBJECT (stream->pad, "%s event was not handled", GST_EVENT_TYPE_NAME (event)); } else { /* If at least one push returns TRUE, then we return TRUE. */ GST_DEBUG_OBJECT (stream->pad, "%s event was handled", GST_EVENT_TYPE_NAME (event)); ret = TRUE; } } } gst_event_unref (event); return ret; } static gboolean gst_ps_demux_handle_dvd_event (GstPsDemux * demux, GstEvent * event) { const GstStructure *structure = gst_event_get_structure (event); const char *type = gst_structure_get_string (structure, "event"); gint i; gchar cur_stream_name[32]; GstPsStream *temp = NULL; const gchar *lang_code; if (strcmp (type, "dvd-lang-codes") == 0) { GST_DEBUG_OBJECT (demux, "Handling language codes event"); /* Create a video pad to ensure have it before emit no more pads */ (void) gst_ps_demux_get_stream (demux, 0xe0, ST_VIDEO_MPEG2); /* Read out the languages for audio streams and request each one that * is present */ for (i = 0; i < MAX_DVD_AUDIO_STREAMS; i++) { gint stream_format; gint stream_id; g_snprintf (cur_stream_name, 32, "audio-%d-format", i); if (!gst_structure_get_int (structure, cur_stream_name, &stream_format)) continue; g_snprintf (cur_stream_name, 32, "audio-%d-stream", i); if (!gst_structure_get_int (structure, cur_stream_name, &stream_id)) continue; if (stream_id < 0 || stream_id >= MAX_DVD_AUDIO_STREAMS) continue; switch (stream_format) { case 0x0: /* AC3 */ stream_id += 0x80; GST_DEBUG_OBJECT (demux, "Audio stream %d format %d ID 0x%02x - AC3", i, stream_format, stream_id); temp = gst_ps_demux_get_stream (demux, stream_id, ST_PS_AUDIO_AC3); break; case 0x2: case 0x3: /* MPEG audio without and with extension stream are * treated the same */ stream_id += 0xC0; GST_DEBUG_OBJECT (demux, "Audio stream %d format %d ID 0x%02x - MPEG audio", i, stream_format, stream_id); temp = gst_ps_demux_get_stream (demux, stream_id, ST_AUDIO_MPEG1); break; case 0x4: /* LPCM */ stream_id += 0xA0; GST_DEBUG_OBJECT (demux, "Audio stream %d format %d ID 0x%02x - DVD LPCM", i, stream_format, stream_id); temp = gst_ps_demux_get_stream (demux, stream_id, ST_PS_AUDIO_LPCM); break; case 0x6: /* DTS */ stream_id += 0x88; GST_DEBUG_OBJECT (demux, "Audio stream %d format %d ID 0x%02x - DTS", i, stream_format, stream_id); temp = gst_ps_demux_get_stream (demux, stream_id, ST_PS_AUDIO_DTS); break; case 0x7: /* FIXME: What range is SDDS? */ default: GST_WARNING_OBJECT (demux, "Unknown audio stream format in language code event: %d", stream_format); temp = NULL; continue; } g_snprintf (cur_stream_name, 32, "audio-%d-language", i); lang_code = gst_structure_get_string (structure, cur_stream_name); if (lang_code) { GstTagList *list = temp->pending_tags; if (!list) list = gst_tag_list_new_empty (); gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_LANGUAGE_CODE, lang_code, NULL); temp->pending_tags = list; } } /* And subtitle streams */ for (i = 0; i < MAX_DVD_SUBPICTURE_STREAMS; i++) { gint stream_id; g_snprintf (cur_stream_name, 32, "subpicture-%d-format", i); if (!gst_structure_get_int (structure, cur_stream_name, &stream_id)) continue; g_snprintf (cur_stream_name, 32, "subpicture-%d-stream", i); if (!gst_structure_get_int (structure, cur_stream_name, &stream_id)) continue; if (stream_id < 0 || stream_id >= MAX_DVD_SUBPICTURE_STREAMS) continue; GST_DEBUG_OBJECT (demux, "Subpicture stream %d ID 0x%02x", i, 0x20 + stream_id); /* Retrieve the subpicture stream to force pad creation */ temp = gst_ps_demux_get_stream (demux, 0x20 + stream_id, ST_PS_DVD_SUBPICTURE); g_snprintf (cur_stream_name, 32, "subpicture-%d-language", i); lang_code = gst_structure_get_string (structure, cur_stream_name); if (lang_code) { GstTagList *list = temp->pending_tags; if (!list) list = gst_tag_list_new_empty (); gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_LANGUAGE_CODE, lang_code, NULL); temp->pending_tags = list; } } GST_DEBUG_OBJECT (demux, "Created all pads from Language Codes event, " "signalling no-more-pads"); gst_element_no_more_pads (GST_ELEMENT (demux)); demux->need_no_more_pads = FALSE; } else { /* forward to all pads, e.g. dvd clut event */ gst_event_ref (event); gst_ps_demux_send_event (demux, event); } gst_event_unref (event); return TRUE; } static void gst_ps_demux_flush (GstPsDemux * demux) { GST_DEBUG_OBJECT (demux, "flushing demuxer"); gst_adapter_clear (demux->adapter); gst_adapter_clear (demux->rev_adapter); gst_pes_filter_drain (&demux->filter); gst_ps_demux_clear_times (demux); demux->adapter_offset = G_MAXUINT64; demux->current_scr = G_MAXUINT64; demux->bytes_since_scr = 0; } static inline void gst_ps_demux_clear_times (GstPsDemux * demux) { gint i, count = demux->found_count; gst_flow_combiner_reset (demux->flowcombiner); /* Clear the last ts for all streams */ for (i = 0; i < count; i++) { GstPsStream *stream = demux->streams_found[i]; if (G_LIKELY (stream)) { stream->last_ts = GST_CLOCK_TIME_NONE; } } } static inline void gst_ps_demux_send_gap_updates (GstPsDemux * demux, GstClockTime new_start) { GstClockTime base_time, stop; gint i, count = demux->found_count; GstEvent *event = NULL; /* Advance all lagging streams by sending a gap event */ if ((base_time = demux->base_time) == GST_CLOCK_TIME_NONE) base_time = 0; stop = demux->src_segment.stop; if (stop != GST_CLOCK_TIME_NONE) stop += base_time; if (new_start > stop) return; /* FIXME: Handle reverse playback */ for (i = 0; i < count; i++) { GstPsStream *stream = demux->streams_found[i]; if (stream) { if (stream->last_ts == GST_CLOCK_TIME_NONE || stream->last_ts < demux->src_segment.start + base_time) stream->last_ts = demux->src_segment.start + base_time; if (stream->last_ts + stream->segment_thresh < new_start) { /* should send segment info before gap event */ gst_ps_demux_send_segment (demux, stream, GST_CLOCK_TIME_NONE); GST_LOG_OBJECT (demux, "Sending gap update to pad %s time %" GST_TIME_FORMAT, GST_PAD_NAME (stream->pad), GST_TIME_ARGS (new_start)); event = gst_event_new_gap (stream->last_ts, new_start - stream->last_ts); gst_pad_push_event (stream->pad, event); stream->last_ts = new_start; } } } } static inline gboolean have_open_streams (GstPsDemux * demux) { return (demux->streams_found[0] != NULL); } static gboolean gst_ps_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean res = TRUE; GstPsDemux *demux = GST_PS_DEMUX (parent); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_FLUSH_START: gst_ps_demux_send_event (demux, event); break; case GST_EVENT_FLUSH_STOP: gst_ps_demux_send_event (demux, event); gst_segment_init (&demux->sink_segment, GST_FORMAT_UNDEFINED); gst_ps_demux_flush (demux); break; case GST_EVENT_SEGMENT: { const GstSegment *segment; gst_event_parse_segment (event, &segment); gst_segment_copy_into (segment, &demux->sink_segment); GST_INFO_OBJECT (demux, "received segment %" GST_SEGMENT_FORMAT, segment); /* we need to emit a new segment */ gst_ps_demux_mark_discont (demux, TRUE, TRUE); if (segment->format == GST_FORMAT_BYTES && demux->scr_rate_n != G_MAXUINT64 && demux->scr_rate_d != G_MAXUINT64) { demux->src_segment.rate = segment->rate; demux->src_segment.applied_rate = segment->applied_rate; demux->src_segment.format = GST_FORMAT_TIME; demux->src_segment.start = BYTES_TO_GSTTIME (segment->start); demux->src_segment.stop = BYTES_TO_GSTTIME (segment->stop); demux->src_segment.time = BYTES_TO_GSTTIME (segment->time); } else if (segment->format == GST_FORMAT_TIME) { /* we expect our timeline (SCR, PTS) to match the one from upstream, * if not, will adjust with offset later on */ gst_segment_copy_into (segment, &demux->src_segment); /* accept upstream segment without adjusting */ demux->adjust_segment = FALSE; } gst_event_unref (event); break; } case GST_EVENT_EOS: GST_INFO_OBJECT (demux, "Received EOS"); if (!gst_ps_demux_send_event (demux, event) && !have_open_streams (demux)) { GST_WARNING_OBJECT (demux, "EOS and no streams open"); GST_ELEMENT_ERROR (demux, STREAM, FAILED, ("Internal data stream error."), ("No valid streams detected")); } break; case GST_EVENT_CUSTOM_DOWNSTREAM: case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: { const GstStructure *structure = gst_event_get_structure (event); if (structure != NULL && gst_structure_has_name (structure, "application/x-gst-dvd")) { res = gst_ps_demux_handle_dvd_event (demux, event); } else { gst_ps_demux_send_event (demux, event); } break; } case GST_EVENT_CAPS: gst_event_unref (event); break; default: gst_ps_demux_send_event (demux, event); break; } return res; } static gboolean gst_ps_demux_handle_seek_push (GstPsDemux * demux, GstEvent * event) { gboolean res = FALSE; gdouble rate; GstFormat format; GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 start, stop; gint64 bstart, bstop; GstEvent *bevent; gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); GST_DEBUG_OBJECT (demux, "seek event, rate: %f start: %" GST_TIME_FORMAT " stop: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); if (format == GST_FORMAT_BYTES) { GST_DEBUG_OBJECT (demux, "seek not supported on format %d", format); goto not_supported; } GST_DEBUG_OBJECT (demux, "seek - trying directly upstream first"); /* first try original format seek */ (void) gst_event_ref (event); if ((res = gst_pad_push_event (demux->sinkpad, event))) goto done; if (format != GST_FORMAT_TIME) { /* From here down, we only support time based seeks */ GST_DEBUG_OBJECT (demux, "seek not supported on format %d", format); goto not_supported; } /* We need to convert to byte based seek and we need a scr_rate for that. */ if (demux->scr_rate_n == G_MAXUINT64 || demux->scr_rate_d == G_MAXUINT64) { GST_DEBUG_OBJECT (demux, "seek not possible, no scr_rate"); goto not_supported; } GST_DEBUG_OBJECT (demux, "try with scr_rate interpolation"); bstart = GSTTIME_TO_BYTES ((guint64) start); bstop = GSTTIME_TO_BYTES ((guint64) stop); GST_DEBUG_OBJECT (demux, "in bytes bstart %" G_GINT64_FORMAT " bstop %" G_GINT64_FORMAT, bstart, bstop); bevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, start_type, bstart, stop_type, bstop); res = gst_pad_push_event (demux->sinkpad, bevent); done: gst_event_unref (event); return res; not_supported: { gst_event_unref (event); return FALSE; } } #define MAX_RECURSION_COUNT 100 /* Binary search for requested SCR */ static inline guint64 find_offset (GstPsDemux * demux, guint64 scr, guint64 min_scr, guint64 min_scr_offset, guint64 max_scr, guint64 max_scr_offset, int recursion_count) { guint64 scr_rate_n = max_scr_offset - min_scr_offset; guint64 scr_rate_d = max_scr - min_scr; guint64 fscr = scr; guint64 offset; if (recursion_count > MAX_RECURSION_COUNT) { return -1; } offset = min_scr_offset + MIN (gst_util_uint64_scale (scr - min_scr, scr_rate_n, scr_rate_d), demux->sink_segment.stop); if (!gst_ps_demux_scan_forward_ts (demux, &offset, SCAN_SCR, &fscr, 0)) { gst_ps_demux_scan_backward_ts (demux, &offset, SCAN_SCR, &fscr, 0); } if (fscr == scr || fscr == min_scr || fscr == max_scr) { return offset; } if (fscr < scr) { return find_offset (demux, scr, fscr, offset, max_scr, max_scr_offset, recursion_count + 1); } else { return find_offset (demux, scr, min_scr, min_scr_offset, fscr, offset, recursion_count + 1); } } static inline gboolean gst_ps_demux_do_seek (GstPsDemux * demux, GstSegment * seeksegment) { gboolean found; guint64 fscr, offset; guint64 scr = GSTTIME_TO_MPEGTIME (seeksegment->position + demux->base_time); /* In some clips the PTS values are completely unaligned with SCR values. * To improve the seek in that situation we apply a factor considering the * relationship between last PTS and last SCR */ if (demux->last_scr > demux->last_pts) scr = gst_util_uint64_scale (scr, demux->last_scr, demux->last_pts); scr = MIN (demux->last_scr, scr); scr = MAX (demux->first_scr, scr); fscr = scr; GST_INFO_OBJECT (demux, "sink segment configured %" GST_SEGMENT_FORMAT ", trying to go at SCR: %" G_GUINT64_FORMAT, &demux->sink_segment, scr); offset = find_offset (demux, scr, demux->first_scr, demux->first_scr_offset, demux->last_scr, demux->last_scr_offset, 0); if (offset == (guint64) - 1) { return FALSE; } found = gst_ps_demux_scan_forward_ts (demux, &offset, SCAN_SCR, &fscr, 0); if (!found) found = gst_ps_demux_scan_backward_ts (demux, &offset, SCAN_SCR, &fscr, 0); while (found && fscr < scr) { offset++; found = gst_ps_demux_scan_forward_ts (demux, &offset, SCAN_SCR, &fscr, 0); } while (found && fscr > scr && offset > 0) { offset--; found = gst_ps_demux_scan_backward_ts (demux, &offset, SCAN_SCR, &fscr, 0); } GST_INFO_OBJECT (demux, "doing seek at offset %" G_GUINT64_FORMAT " SCR: %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT, offset, fscr, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (fscr))); gst_segment_set_position (&demux->sink_segment, GST_FORMAT_BYTES, offset); return TRUE; } static gboolean gst_ps_demux_handle_seek_pull (GstPsDemux * demux, GstEvent * event) { GstFormat format; GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 start, stop; gdouble rate; gboolean update, flush; GstSegment seeksegment; GstClockTime first_pts = MPEGTIME_TO_GSTTIME (demux->first_pts); gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); if (format != GST_FORMAT_TIME) goto wrong_format; GST_DEBUG_OBJECT (demux, "Seek requested start %" GST_TIME_FORMAT " stop %" GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); /* We need to convert to byte based seek and we need a scr_rate for that. */ if (demux->scr_rate_n == G_MAXUINT64 || demux->scr_rate_d == G_MAXUINT64) goto no_scr_rate; flush = flags & GST_SEEK_FLAG_FLUSH; /* keyframe = flags & GST_SEEK_FLAG_KEY_UNIT; *//* FIXME */ if (flush) { /* Flush start up and downstream to make sure data flow and loops are idle */ demux->flushing = TRUE; gst_ps_demux_send_event (demux, gst_event_new_flush_start ()); gst_pad_push_event (demux->sinkpad, gst_event_new_flush_start ()); } else { /* Pause the pulling task */ gst_pad_pause_task (demux->sinkpad); } /* Take the stream lock */ GST_PAD_STREAM_LOCK (demux->sinkpad); if (flush) { /* Stop flushing upstream we need to pull */ demux->flushing = FALSE; gst_pad_push_event (demux->sinkpad, gst_event_new_flush_stop (TRUE)); } /* Work on a copy until we are sure the seek succeeded. */ memcpy (&seeksegment, &demux->src_segment, sizeof (GstSegment)); GST_DEBUG_OBJECT (demux, "segment before configure %" GST_SEGMENT_FORMAT, &demux->src_segment); /* Apply the seek to our segment */ if (!gst_segment_do_seek (&seeksegment, rate, format, flags, start_type, start, stop_type, stop, &update)) goto seek_error; GST_DEBUG_OBJECT (demux, "seek segment configured %" GST_SEGMENT_FORMAT, &seeksegment); if (flush || seeksegment.position != demux->src_segment.position) { /* Do the actual seeking */ if (!gst_ps_demux_do_seek (demux, &seeksegment)) { return FALSE; } } /* check the limits */ if (seeksegment.rate > 0.0) { if (seeksegment.start < first_pts - demux->base_time) { seeksegment.start = first_pts - demux->base_time; seeksegment.position = seeksegment.start; } } /* update the rate in our src segment */ demux->sink_segment.rate = rate; GST_DEBUG_OBJECT (demux, "seek segment adjusted %" GST_SEGMENT_FORMAT, &seeksegment); if (flush) { /* Stop flushing, the sinks are at time 0 now */ gst_ps_demux_send_event (demux, gst_event_new_flush_stop (TRUE)); } if (flush || seeksegment.position != demux->src_segment.position) { gst_ps_demux_flush (demux); } /* Ok seek succeeded, take the newly configured segment */ memcpy (&demux->src_segment, &seeksegment, sizeof (GstSegment)); /* Notify about the start of a new segment */ if (demux->src_segment.flags & GST_SEEK_FLAG_SEGMENT) { gst_element_post_message (GST_ELEMENT (demux), gst_message_new_segment_start (GST_OBJECT (demux), demux->src_segment.format, demux->src_segment.position)); } /* Tell all the stream a new segment is needed */ gst_ps_demux_mark_discont (demux, TRUE, TRUE); gst_pad_start_task (demux->sinkpad, (GstTaskFunction) gst_ps_demux_loop, demux->sinkpad, NULL); GST_PAD_STREAM_UNLOCK (demux->sinkpad); gst_event_unref (event); return TRUE; /* ERRORS */ wrong_format: { GST_WARNING_OBJECT (demux, "we only support seeking in TIME or BYTES " "formats"); gst_event_unref (event); return FALSE; } no_scr_rate: { GST_WARNING_OBJECT (demux, "seek not possible, no scr_rate"); gst_event_unref (event); return FALSE; } seek_error: { GST_WARNING_OBJECT (demux, "couldn't perform seek"); gst_event_unref (event); return FALSE; } } static gboolean gst_ps_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean res = FALSE; GstPsDemux *demux = GST_PS_DEMUX (parent); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: if (demux->random_access) { res = gst_ps_demux_handle_seek_pull (demux, event); } else { res = gst_ps_demux_handle_seek_push (demux, event); } break; case GST_EVENT_RECONFIGURE:{ GstPsStream *stream; stream = gst_ps_demux_get_stream_from_pad (demux, pad); if (stream != NULL) stream->notlinked = FALSE; gst_event_unref (event); res = TRUE; break; } default: res = gst_pad_push_event (demux->sinkpad, event); break; } return res; } static gboolean gst_ps_demux_src_query (GstPad * pad, GstObject * parent, GstQuery * query) { gboolean res = FALSE; GstPsDemux *demux = GST_PS_DEMUX (parent); GST_LOG_OBJECT (demux, "Have query of type %d on pad %" GST_PTR_FORMAT, GST_QUERY_TYPE (query), pad); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_POSITION: { GstClockTime pos; GstFormat format; /* See if upstream can immediately answer */ res = gst_pad_peer_query (demux->sinkpad, query); if (res) break; gst_query_parse_position (query, &format, NULL); if (format != GST_FORMAT_TIME) { GST_DEBUG_OBJECT (demux, "position not supported for format: %s", gst_format_get_name (format)); goto not_supported; } pos = demux->src_segment.position - demux->src_segment.start; GST_LOG_OBJECT (demux, "Position %" GST_TIME_FORMAT, GST_TIME_ARGS (pos)); gst_query_set_position (query, format, pos); res = TRUE; break; } case GST_QUERY_DURATION: { GstFormat format; gint64 duration; GstQuery *byte_query; gst_query_parse_duration (query, &format, NULL); if (G_LIKELY (format == GST_FORMAT_TIME && GST_CLOCK_TIME_IS_VALID (demux->src_segment.duration))) { gst_query_set_duration (query, GST_FORMAT_TIME, demux->src_segment.duration); res = TRUE; break; } /* For any format other than bytes, see if upstream knows first */ if (format == GST_FORMAT_BYTES) { GST_DEBUG_OBJECT (demux, "duration not supported for format: %s", gst_format_get_name (format)); goto not_supported; } if (gst_pad_peer_query (demux->sinkpad, query)) { res = TRUE; break; } /* Upstream didn't know, so we can only answer TIME queries from * here on */ if (format != GST_FORMAT_TIME) { GST_DEBUG_OBJECT (demux, "duration not supported for format: %s", gst_format_get_name (format)); goto not_supported; } if (demux->mux_rate == -1) { GST_DEBUG_OBJECT (demux, "duration not possible, no mux_rate"); goto not_supported; } byte_query = gst_query_new_duration (GST_FORMAT_BYTES); if (!gst_pad_peer_query (demux->sinkpad, byte_query)) { GST_LOG_OBJECT (demux, "query on peer pad failed"); gst_query_unref (byte_query); goto not_supported; } gst_query_parse_duration (byte_query, &format, &duration); gst_query_unref (byte_query); GST_LOG_OBJECT (demux, "query on peer pad reported bytes %" G_GUINT64_FORMAT, duration); duration = BYTES_TO_GSTTIME ((guint64) duration); GST_LOG_OBJECT (demux, "converted to time %" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); gst_query_set_duration (query, GST_FORMAT_TIME, duration); res = TRUE; break; } case GST_QUERY_SEEKING:{ GstFormat fmt; gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); res = TRUE; if (demux->random_access) { /* In pull mode we can seek in TIME format if we have the SCR */ if (fmt != GST_FORMAT_TIME || demux->scr_rate_n == G_MAXUINT64 || demux->scr_rate_d == G_MAXUINT64) gst_query_set_seeking (query, fmt, FALSE, -1, -1); else gst_query_set_seeking (query, fmt, TRUE, 0, -1); } else { if (fmt == GST_FORMAT_BYTES) { /* Seeking in BYTES format not supported at all */ gst_query_set_seeking (query, fmt, FALSE, -1, -1); } else { GstQuery *peerquery; gboolean seekable; /* Then ask upstream */ res = gst_pad_peer_query (demux->sinkpad, query); if (res) { /* If upstream can handle seeks we're done, if it * can't we still have our TIME->BYTES conversion seek */ gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL); if (seekable || fmt != GST_FORMAT_TIME) goto beach; } /* We can seek if upstream supports BYTES seeks and we * have the SCR */ peerquery = gst_query_new_seeking (GST_FORMAT_BYTES); res = gst_pad_peer_query (demux->sinkpad, peerquery); if (!res || demux->scr_rate_n == G_MAXUINT64 || demux->scr_rate_d == G_MAXUINT64) { gst_query_set_seeking (query, fmt, FALSE, -1, -1); } else { gst_query_parse_seeking (peerquery, NULL, &seekable, NULL, NULL); if (seekable) gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, -1); else gst_query_set_seeking (query, fmt, FALSE, -1, -1); } gst_query_unref (peerquery); res = TRUE; } } break; } case GST_QUERY_SEGMENT:{ GstFormat format; gint64 start, stop; format = demux->src_segment.format; start = gst_segment_to_stream_time (&demux->src_segment, format, demux->src_segment.start); if ((stop = demux->src_segment.stop) == -1) stop = demux->src_segment.duration; else stop = gst_segment_to_stream_time (&demux->src_segment, format, stop); gst_query_set_segment (query, demux->src_segment.rate, format, start, stop); res = TRUE; break; } default: res = gst_pad_query_default (pad, parent, query); break; } beach: return res; not_supported: return FALSE; } static void gst_ps_demux_reset_psm (GstPsDemux * demux) { gint i; #define FILL_TYPE(start, stop, type) \ for (i=start; i <= stop; i++) \ demux->psm[i] = type; /* Initialize all fields to -1 first */ FILL_TYPE (0x00, GST_PS_DEMUX_MAX_PSM - 1, -1); FILL_TYPE (0x20, 0x3f, ST_PS_DVD_SUBPICTURE); FILL_TYPE (0x80, 0x87, ST_PS_AUDIO_AC3); FILL_TYPE (0x88, 0x9f, ST_PS_AUDIO_DTS); FILL_TYPE (0xa0, 0xaf, ST_PS_AUDIO_LPCM); FILL_TYPE (0xc0, 0xdf, ST_AUDIO_MPEG1); FILL_TYPE (0xe0, 0xef, ST_GST_VIDEO_MPEG1_OR_2); #undef FILL_TYPE } /* ISO/IEC 13818-1: * pack_header() { * pack_start_code 32 bslbf -+ * '01' 2 bslbf | * system_clock_reference_base [32..30] 3 bslbf | * marker_bit 1 bslbf | * system_clock_reference_base [29..15] 15 bslbf | * marker_bit 1 bslbf | * system_clock_reference_base [14..0] 15 bslbf | * marker_bit 1 bslbf | 112 bits * system_clock_reference_extension 9 ubslbf | * marker_bit 1 bslbf | * program_mux_rate 22 ubslbf | * marker_bit 1 bslbf | * marker_bit 1 bslbf | * reserved 5 bslbf | * pack_stuffing_length 3 ubslbf -+ * * for (i = 0; i < pack_stuffing_length; i++) { * stuffing_byte '1111 1111' 8 bslbf * } * * 112 bits = 14 bytes, as max value for pack_stuffing_length is 7, then * in total it's needed 14 + 7 = 21 bytes. */ #define PACK_START_SIZE 21 static GstFlowReturn gst_ps_demux_parse_pack_start (GstPsDemux * demux) { const guint8 *data; guint length; guint32 scr1, scr2; guint64 scr, scr_adjusted, new_rate; guint64 scr_rate_n; guint64 scr_rate_d; guint avail = gst_adapter_available (demux->adapter); GST_LOG ("parsing pack start"); if (G_UNLIKELY (avail < PACK_START_SIZE)) goto need_more_data; data = gst_adapter_map (demux->adapter, PACK_START_SIZE); /* skip start code */ data += 4; scr1 = GST_READ_UINT32_BE (data); scr2 = GST_READ_UINT32_BE (data + 4); /* fixed length to begin with, start code and two scr values */ length = 8 + 4; /* start parsing the stream */ if ((*data & 0xc0) == 0x40) { guint32 scr_ext; guint32 next32; guint8 stuffing_bytes; GST_LOG ("Found MPEG2 stream"); demux->is_mpeg2_pack = TRUE; /* mpeg2 has more data */ length += 2; /* :2=01 ! scr:3 ! marker:1==1 ! scr:15 ! marker:1==1 ! scr:15 */ /* check markers */ if (G_UNLIKELY ((scr1 & 0xc4000400) != 0x44000400)) goto lost_sync; scr = ((guint64) scr1 & 0x38000000) << 3; scr |= ((guint64) scr1 & 0x03fff800) << 4; scr |= ((guint64) scr1 & 0x000003ff) << 5; scr |= ((guint64) scr2 & 0xf8000000) >> 27; /* marker:1==1 ! scr_ext:9 ! marker:1==1 */ if (G_UNLIKELY ((scr2 & 0x04010000) != 0x04010000)) goto lost_sync; scr_ext = (scr2 & 0x03fe0000) >> 17; /* We keep the offset of this scr */ demux->cur_scr_offset = demux->adapter_offset + 12; GST_LOG_OBJECT (demux, "SCR: 0x%08" G_GINT64_MODIFIER "x SCRE: 0x%08x", scr, scr_ext); if (scr_ext) { scr = (scr * 300 + scr_ext % 300) / 300; } /* SCR has been converted into units of 90Khz ticks to make it comparable to DTS/PTS, that also implies 1 tick rounding error */ data += 6; /* PMR:22 ! :2==11 ! reserved:5 ! stuffing_len:3 */ next32 = GST_READ_UINT32_BE (data); if (G_UNLIKELY ((next32 & 0x00000300) != 0x00000300)) goto lost_sync; new_rate = (next32 & 0xfffffc00) >> 10; stuffing_bytes = (next32 & 0x07); GST_LOG_OBJECT (demux, "stuffing bytes: %d", stuffing_bytes); data += 4; length += stuffing_bytes; while (stuffing_bytes--) { if (*data++ != 0xff) goto lost_sync; } } else { GST_DEBUG ("Found MPEG1 stream"); demux->is_mpeg2_pack = FALSE; /* check markers */ if (G_UNLIKELY ((scr1 & 0xf1000100) != 0x21000100)) goto lost_sync; if (G_UNLIKELY ((scr2 & 0x01800001) != 0x01800001)) goto lost_sync; /* :4=0010 ! scr:3 ! marker:1==1 ! scr:15 ! marker:1==1 ! scr:15 ! marker:1==1 */ scr = ((guint64) scr1 & 0x0e000000) << 5; scr |= ((guint64) scr1 & 0x00fffe00) << 6; scr |= ((guint64) scr1 & 0x000000ff) << 7; scr |= ((guint64) scr2 & 0xfe000000) >> 25; /* We keep the offset of this scr */ demux->cur_scr_offset = demux->adapter_offset + 8; /* marker:1==1 ! mux_rate:22 ! marker:1==1 */ new_rate = (scr2 & 0x007ffffe) >> 1; data += 8; } new_rate *= MPEG_MUX_RATE_MULT; /* scr adjusted is the new scr found + the colected adjustment */ scr_adjusted = scr + demux->scr_adjust; GST_LOG_OBJECT (demux, "SCR: %" G_GINT64_FORMAT " (%" G_GINT64_FORMAT "), mux_rate %" G_GINT64_FORMAT ", GStreamer Time:%" GST_TIME_FORMAT, scr, scr_adjusted, new_rate, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME ((guint64) scr))); /* keep the first src in order to calculate delta time */ if (G_UNLIKELY (demux->first_scr == G_MAXUINT64)) { gint64 diff; demux->first_scr = scr; demux->first_scr_offset = demux->cur_scr_offset; demux->base_time = MPEGTIME_TO_GSTTIME (demux->first_scr); GST_DEBUG_OBJECT (demux, "determined base_time %" GST_TIME_FORMAT, GST_TIME_ARGS (demux->base_time)); /* at begin consider the new_rate as the scr rate, bytes/clock ticks */ scr_rate_n = new_rate; scr_rate_d = CLOCK_FREQ; /* our SCR timeline might have offset wrt upstream timeline */ if (demux->sink_segment.format == GST_FORMAT_TIME) { if (demux->sink_segment.start > demux->base_time) diff = -(demux->sink_segment.start - demux->base_time); else diff = demux->base_time - demux->sink_segment.start; if (diff > GST_SECOND) { GST_DEBUG_OBJECT (demux, "diff of %" GST_TIME_FORMAT " wrt upstream start %" GST_TIME_FORMAT "; adjusting base", GST_TIME_ARGS (diff), GST_TIME_ARGS (demux->sink_segment.start)); demux->base_time += diff; } } } else if (G_LIKELY (demux->first_scr_offset != demux->cur_scr_offset)) { /* estimate byte rate related to the SCR */ scr_rate_n = demux->cur_scr_offset - demux->first_scr_offset; scr_rate_d = scr_adjusted - demux->first_scr; } else { scr_rate_n = demux->scr_rate_n; scr_rate_d = demux->scr_rate_d; } GST_LOG_OBJECT (demux, "%s mode scr: %" G_GUINT64_FORMAT " at %" G_GUINT64_FORMAT ", first scr: %" G_GUINT64_FORMAT " at %" G_GUINT64_FORMAT ", scr rate: %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT "(%f)", ((demux->sink_segment.rate >= 0.0) ? "forward" : "backward"), scr, demux->cur_scr_offset, demux->first_scr, demux->first_scr_offset, scr_rate_n, scr_rate_d, (float) scr_rate_n / scr_rate_d); /* adjustment of the SCR */ if (G_LIKELY (demux->current_scr != G_MAXUINT64)) { guint64 diff; guint64 old_scr, old_mux_rate, bss, adjust = 0; /* keep SCR of the previous packet */ old_scr = demux->current_scr; old_mux_rate = demux->mux_rate; /* Bytes since SCR is the amount we placed in the adapter since then * (demux->bytes_since_scr) minus the amount remaining in the adapter, * clamped to >= 0 */ bss = MAX (0, (gint) (demux->bytes_since_scr - avail)); /* estimate the new SCR using the previous one according the notes on point 2.5.2.2 of the ISO/IEC 13818-1 document */ if (old_mux_rate != 0) adjust = (bss * CLOCK_FREQ) / old_mux_rate; if (demux->sink_segment.rate >= 0.0) demux->next_scr = old_scr + adjust; else demux->next_scr = old_scr - adjust; GST_LOG_OBJECT (demux, "bss: %" G_GUINT64_FORMAT ", next_scr: %" G_GUINT64_FORMAT ", old_scr: %" G_GUINT64_FORMAT ", scr: %" G_GUINT64_FORMAT, bss, demux->next_scr, old_scr, scr_adjusted); /* calculate the absolute deference between the last scr and the new one */ if (G_UNLIKELY (old_scr > scr_adjusted)) diff = old_scr - scr_adjusted; else diff = scr_adjusted - old_scr; /* if the difference is more than 1 second we need to reconfigure adjustment */ if (G_UNLIKELY (diff > CLOCK_FREQ)) { demux->scr_adjust = demux->next_scr - scr; GST_LOG_OBJECT (demux, "discont found, diff: %" G_GINT64_FORMAT ", adjust %" G_GINT64_FORMAT, diff, demux->scr_adjust); scr_adjusted = demux->next_scr; /* don't update rate estimation on disconts */ scr_rate_n = demux->scr_rate_n; scr_rate_d = demux->scr_rate_d; } else { demux->next_scr = scr_adjusted; } } /* update the current_scr and rate members */ demux->mux_rate = new_rate; demux->current_scr = scr_adjusted; demux->scr_rate_n = scr_rate_n; demux->scr_rate_d = scr_rate_d; /* Reset the bytes_since_scr value to count the data remaining in the * adapter */ demux->bytes_since_scr = avail; gst_adapter_unmap (demux->adapter); gst_adapter_flush (demux->adapter, length); ADAPTER_OFFSET_FLUSH (length); return GST_FLOW_OK; lost_sync: { GST_DEBUG_OBJECT (demux, "lost sync"); gst_adapter_unmap (demux->adapter); return GST_FLOW_LOST_SYNC; } need_more_data: { GST_DEBUG_OBJECT (demux, "need more data"); return GST_FLOW_NEED_MORE_DATA; } } /* ISO/IEC 13818-1: * system_header () { * system_header_start_code 32 bslbf -+ * header_length 16 uimsbf | * marker_bit 1 bslbf | * rate_bound 22 uimsbf | * marker_bit 1 bslbf | * audio_bound 6 uimsbf | * fixed_flag 1 bslbf | * CSPS_flag 1 bslbf | 96 bits * system_audio_lock_flag 1 bslbf | * system_video_lock_flag 1 bslbf | * marker_bit 1 bslbf | * video_bound 5 uimsbf | * packet_rate_restriction_flag 1 bslbf | * reserved_bits 7 bslbf -+ * while (nextbits () = = '1') { * stream_id 8 uimsbf -+ * '11' 2 bslbf | 24 bits * P-STD_buffer_bound_scale 1 bslbf | * P-STD_buffer_size_bound 13 uimsbf -+ * } * } * 96 bits = 12 bytes, 24 bits = 3 bytes. */ static GstFlowReturn gst_ps_demux_parse_sys_head (GstPsDemux * demux) { guint16 length; const guint8 *data; #ifndef GST_DISABLE_GST_DEBUG gboolean csps; #endif if (gst_adapter_available (demux->adapter) < 6) goto need_more_data; /* start code + length */ data = gst_adapter_map (demux->adapter, 6); /* skip start code */ data += 4; length = GST_READ_UINT16_BE (data); GST_DEBUG_OBJECT (demux, "length %d", length); length += 6; gst_adapter_unmap (demux->adapter); if (gst_adapter_available (demux->adapter) < length) goto need_more_data; data = gst_adapter_map (demux->adapter, length); /* skip start code and length */ data += 6; /* marker:1==1 ! rate_bound:22 | marker:1==1 */ if ((*data & 0x80) != 0x80) goto marker_expected; { guint32 rate_bound; if ((data[2] & 0x01) != 0x01) goto marker_expected; rate_bound = ((guint32) data[0] & 0x7f) << 15; rate_bound |= ((guint32) data[1]) << 7; rate_bound |= ((guint32) data[2] & 0xfe) >> 1; rate_bound *= MPEG_MUX_RATE_MULT; GST_DEBUG_OBJECT (demux, "rate bound %u", rate_bound); data += 3; } /* audio_bound:6==1 ! fixed:1 | constrained:1 */ { #ifndef GST_DISABLE_GST_DEBUG guint8 audio_bound; gboolean fixed; /* max number of simultaneous audio streams active */ audio_bound = (data[0] & 0xfc) >> 2; /* fixed or variable bitrate */ fixed = (data[0] & 0x02) == 0x02; /* meeting constraints */ csps = (data[0] & 0x01) == 0x01; GST_DEBUG_OBJECT (demux, "audio_bound %d, fixed %d, constrained %d", audio_bound, fixed, csps); #endif data += 1; } /* audio_lock:1 | video_lock:1 | marker:1==1 | video_bound:5 */ { #ifndef GST_DISABLE_GST_DEBUG gboolean audio_lock; gboolean video_lock; guint8 video_bound; audio_lock = (data[0] & 0x80) == 0x80; video_lock = (data[0] & 0x40) == 0x40; #endif if ((data[0] & 0x20) != 0x20) goto marker_expected; #ifndef GST_DISABLE_GST_DEBUG /* max number of simultaneous video streams active */ video_bound = (data[0] & 0x1f); GST_DEBUG_OBJECT (demux, "audio_lock %d, video_lock %d, video_bound %d", audio_lock, video_lock, video_bound); #endif data += 1; } /* packet_rate_restriction:1 | reserved:7==0x7F */ { #ifndef GST_DISABLE_GST_DEBUG gboolean packet_rate_restriction; #endif if ((data[0] & 0x7f) != 0x7f) goto marker_expected; #ifndef GST_DISABLE_GST_DEBUG /* only valid if csps is set */ if (csps) { packet_rate_restriction = (data[0] & 0x80) == 0x80; GST_DEBUG_OBJECT (demux, "packet_rate_restriction %d", packet_rate_restriction); } #endif } data += 1; { gint stream_count = (length - 12) / 3; gint i; GST_DEBUG_OBJECT (demux, "number of streams: %d ", stream_count); for (i = 0; i < stream_count; i++) { guint8 stream_id; #ifndef GST_DISABLE_GST_DEBUG gboolean STD_buffer_bound_scale; guint16 STD_buffer_size_bound; guint32 buf_byte_size_bound; #endif stream_id = *data++; if (!(stream_id & 0x80)) goto sys_len_error; /* check marker bits */ if ((*data & 0xC0) != 0xC0) goto no_placeholder_bits; #ifndef GST_DISABLE_GST_DEBUG STD_buffer_bound_scale = *data & 0x20; STD_buffer_size_bound = ((guint16) (*data++ & 0x1F)) << 8; STD_buffer_size_bound |= *data++; if (STD_buffer_bound_scale == 0) { buf_byte_size_bound = STD_buffer_size_bound * 128; } else { buf_byte_size_bound = STD_buffer_size_bound * 1024; } GST_DEBUG_OBJECT (demux, "STD_buffer_bound_scale %d", STD_buffer_bound_scale); GST_DEBUG_OBJECT (demux, "STD_buffer_size_bound %d or %d bytes", STD_buffer_size_bound, buf_byte_size_bound); #endif } } gst_adapter_unmap (demux->adapter); gst_adapter_flush (demux->adapter, length); ADAPTER_OFFSET_FLUSH (length); return GST_FLOW_OK; /* ERRORS */ marker_expected: { GST_DEBUG_OBJECT (demux, "expecting marker"); gst_adapter_unmap (demux->adapter); return GST_FLOW_LOST_SYNC; } no_placeholder_bits: { GST_DEBUG_OBJECT (demux, "expecting placeholder bit values" " '11' after stream id"); gst_adapter_unmap (demux->adapter); return GST_FLOW_LOST_SYNC; } sys_len_error: { GST_DEBUG_OBJECT (demux, "error in system header length"); gst_adapter_unmap (demux->adapter); return GST_FLOW_LOST_SYNC; } need_more_data: { GST_DEBUG_OBJECT (demux, "need more data"); gst_adapter_unmap (demux->adapter); return GST_FLOW_NEED_MORE_DATA; } } static GstFlowReturn gst_ps_demux_parse_psm (GstPsDemux * demux) { guint16 length = 0, info_length = 0, es_map_length = 0; guint8 psm_version = 0; const guint8 *data, *es_map_base; #ifndef GST_DISABLE_GST_DEBUG gboolean applicable; #endif if (gst_adapter_available (demux->adapter) < 6) goto need_more_data; /* start code + length */ data = gst_adapter_map (demux->adapter, 6); /* skip start code */ data += 4; length = GST_READ_UINT16_BE (data); GST_DEBUG_OBJECT (demux, "length %u", length); if (G_UNLIKELY (length > 0x3FA)) goto psm_len_error; length += 6; gst_adapter_unmap (demux->adapter); if (gst_adapter_available (demux->adapter) < length) goto need_more_data; data = gst_adapter_map (demux->adapter, length); /* skip start code and length */ data += 6; /* Read PSM applicable bit together with version */ psm_version = GST_READ_UINT8 (data); #ifndef GST_DISABLE_GST_DEBUG applicable = (psm_version & 0x80) >> 7; #endif psm_version &= 0x1F; GST_DEBUG_OBJECT (demux, "PSM version %u (applicable now %u)", psm_version, applicable); /* Jump over version and marker bit */ data += 2; /* Read PS info length */ info_length = GST_READ_UINT16_BE (data); /* Cap it to PSM length - needed bytes for ES map length and CRC */ info_length = MIN (length - 16, info_length); GST_DEBUG_OBJECT (demux, "PS info length %u bytes", info_length); /* Jump over that section */ data += (2 + info_length); /* Read ES map length */ es_map_length = GST_READ_UINT16_BE (data); /* Cap it to PSM remaining length - CRC */ es_map_length = MIN (length - (16 + info_length), es_map_length); GST_DEBUG_OBJECT (demux, "ES map length %u bytes", es_map_length); /* Jump over the size */ data += 2; /* Now read the ES map */ es_map_base = data; while (es_map_base + 4 <= data + es_map_length) { guint8 stream_type = 0, stream_id = 0; guint16 stream_info_length = 0; stream_type = GST_READ_UINT8 (es_map_base); es_map_base++; stream_id = GST_READ_UINT8 (es_map_base); es_map_base++; stream_info_length = GST_READ_UINT16_BE (es_map_base); es_map_base += 2; /* Cap stream_info_length */ stream_info_length = MIN (data + es_map_length - es_map_base, stream_info_length); GST_DEBUG_OBJECT (demux, "Stream type %02X with id %02X and %u bytes info", stream_type, stream_id, stream_info_length); if (G_LIKELY (stream_id != 0xbd)) demux->psm[stream_id] = stream_type; else { /* Ignore stream type for private_stream_1 and discover it looking at * the stream data. * Fixes demuxing some clips with lpcm that was wrongly declared as * mpeg audio */ GST_DEBUG_OBJECT (demux, "stream type for private_stream_1 ignored"); } es_map_base += stream_info_length; } gst_adapter_unmap (demux->adapter); gst_adapter_flush (demux->adapter, length); ADAPTER_OFFSET_FLUSH (length); return GST_FLOW_OK; psm_len_error: { GST_DEBUG_OBJECT (demux, "error in PSM length"); gst_adapter_unmap (demux->adapter); return GST_FLOW_LOST_SYNC; } need_more_data: { GST_DEBUG_OBJECT (demux, "need more data"); return GST_FLOW_NEED_MORE_DATA; } } static void gst_ps_demux_resync_cb (GstPESFilter * filter, GstPsDemux * demux) { } static GstFlowReturn gst_ps_demux_data_cb (GstPESFilter * filter, gboolean first, GstBuffer * buffer, GstPsDemux * demux) { GstBuffer *out_buf; GstFlowReturn ret = GST_FLOW_OK; gint stream_type; guint32 start_code; guint8 id; GstMapInfo map; gsize datalen; guint offset = 0; gst_buffer_map (buffer, &map, GST_MAP_READ); datalen = map.size; start_code = filter->start_code; id = filter->id; if (first) { /* find the stream type */ stream_type = demux->psm[id]; if (stream_type == -1) { /* no stream type, if PS1, get the new id */ if (start_code == ID_PRIVATE_STREAM_1 && datalen >= 2) { /* VDR writes A52 streams without any header bytes * (see ftp://ftp.mplayerhq.hu/MPlayer/samples/MPEG-VOB/vdr-AC3) */ if (datalen >= 4) { guint hdr = GST_READ_UINT32_BE (map.data); if (G_UNLIKELY ((hdr & 0xffff0000) == AC3_SYNC_WORD)) { id = 0x80; stream_type = demux->psm[id] = ST_GST_AUDIO_RAWA52; GST_DEBUG_OBJECT (demux, "Found VDR raw A52 stream"); } } if (G_LIKELY (stream_type == -1)) { /* new id is in the first byte */ id = map.data[offset++]; datalen--; /* and remap */ stream_type = demux->psm[id]; /* Now, if it's a subpicture stream - no more, otherwise * take the first byte too, since it's the frame count in audio * streams and our backwards compat convention is to strip it off */ if (stream_type != ST_PS_DVD_SUBPICTURE) { /* Number of audio frames in this packet */ #ifndef GST_DISABLE_GST_DEBUG guint8 nframes; nframes = map.data[offset]; GST_LOG_OBJECT (demux, "private type 0x%02x, %d frames", id, nframes); #endif offset++; datalen--; } else { GST_LOG_OBJECT (demux, "private type 0x%02x, stream type %d", id, stream_type); } } } if (stream_type == -1) goto unknown_stream_type; } if (filter->pts != -1) { demux->next_pts = filter->pts + demux->scr_adjust; GST_LOG_OBJECT (demux, "stream 0x%02x PTS = orig %" G_GUINT64_FORMAT " (%" G_GUINT64_FORMAT ")", id, filter->pts, demux->next_pts); } else demux->next_pts = G_MAXUINT64; if (filter->dts != -1) { demux->next_dts = filter->dts + demux->scr_adjust; GST_LOG_OBJECT (demux, "stream 0x%02x DTS = orig %" G_GUINT64_FORMAT " (%" G_GUINT64_FORMAT ")", id, filter->dts, demux->next_dts); } else { demux->next_dts = demux->next_pts; } demux->current_stream = gst_ps_demux_get_stream (demux, id, stream_type); } if (G_UNLIKELY (demux->current_stream == NULL)) { GST_DEBUG_OBJECT (demux, "Dropping buffer for unknown stream id 0x%02x", id); goto done; } /* After 2 seconds of bitstream emit no more pads */ if (demux->need_no_more_pads && (demux->current_scr - demux->first_scr) > 2 * CLOCK_FREQ) { GST_DEBUG_OBJECT (demux, "no more pads, notifying"); gst_element_no_more_pads (GST_ELEMENT_CAST (demux)); demux->need_no_more_pads = FALSE; } /* If the stream is not-linked, don't bother creating a sub-buffer * to send to it, unless we're processing a discont (which resets * the not-linked status and tries again */ if (demux->current_stream->discont) { GST_DEBUG_OBJECT (demux, "stream is discont"); demux->current_stream->notlinked = FALSE; } if (demux->current_stream->notlinked == FALSE) { out_buf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, offset, datalen); ret = gst_ps_demux_send_data (demux, demux->current_stream, out_buf); if (ret == GST_FLOW_NOT_LINKED) { demux->current_stream->notlinked = TRUE; } } done: gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); return ret; /* ERRORS */ unknown_stream_type: { GST_DEBUG_OBJECT (demux, "unknown stream type %02x", id); ret = GST_FLOW_OK; goto done; } } static gboolean gst_ps_demux_resync (GstPsDemux * demux, gboolean save) { const guint8 *data; gint avail; guint32 code; gint offset; gboolean found; avail = gst_adapter_available (demux->adapter); if (G_UNLIKELY (avail < 4)) goto need_data; /* Common case, read 4 bytes an check it */ data = gst_adapter_map (demux->adapter, 4); /* read currect code */ code = GST_READ_UINT32_BE (data); /* The common case is that the sync code is at 0 bytes offset */ if (G_LIKELY ((code & 0xffffff00) == 0x100L)) { GST_LOG_OBJECT (demux, "Found resync code %08x after 0 bytes", code); demux->last_sync_code = code; gst_adapter_unmap (demux->adapter); return TRUE; } /* Otherwise, we are starting at byte 4 and we need to search the sync code in all available data in the adapter */ offset = 4; if (offset >= avail) goto need_data; /* Not enough data to find sync */ data = gst_adapter_map (demux->adapter, avail); do { code = (code << 8) | data[offset++]; found = (code & 0xffffff00) == 0x100L; } while (offset < avail && !found); gst_adapter_unmap (demux->adapter); if (!save || demux->sink_segment.rate >= 0.0) { GST_LOG_OBJECT (demux, "flushing %d bytes", offset - 4); /* forward playback, we can discard and flush the skipped bytes */ gst_adapter_flush (demux->adapter, offset - 4); ADAPTER_OFFSET_FLUSH (offset - 4); } else { if (found) { GST_LOG_OBJECT (demux, "reverse saving %d bytes", offset - 4); /* reverse playback, we keep the flushed bytes and we will append them to * the next buffer in the chain function, which is the previous buffer in * the stream. */ gst_adapter_push (demux->rev_adapter, gst_adapter_take_buffer (demux->adapter, offset - 4)); } else { GST_LOG_OBJECT (demux, "reverse saving %d bytes", avail); /* nothing found, keep all bytes */ gst_adapter_push (demux->rev_adapter, gst_adapter_take_buffer (demux->adapter, avail)); } } if (found) { GST_LOG_OBJECT (demux, "Found resync code %08x after %d bytes", code, offset - 4); demux->last_sync_code = code; } else { GST_LOG_OBJECT (demux, "No resync after skipping %d", offset); } return found; need_data: { GST_LOG_OBJECT (demux, "we need more data for resync %d", avail); return FALSE; } } static inline gboolean gst_ps_demux_is_pes_sync (guint32 sync) { return ((sync & 0xfc) == 0xbc) || ((sync & 0xe0) == 0xc0) || ((sync & 0xf0) == 0xe0); } static inline gboolean gst_ps_demux_scan_ts (GstPsDemux * demux, const guint8 * data, SCAN_MODE mode, guint64 * rts) { gboolean ret = FALSE; guint32 scr1, scr2; guint64 scr; guint64 pts, dts; guint32 code; /* read the 4 bytes for the sync code */ code = GST_READ_UINT32_BE (data); if (G_LIKELY (code != ID_PS_PACK_START_CODE)) goto beach; /* skip start code */ data += 4; scr1 = GST_READ_UINT32_BE (data); scr2 = GST_READ_UINT32_BE (data + 4); /* start parsing the stream */ if ((*data & 0xc0) == 0x40) { guint32 scr_ext; guint32 next32; guint8 stuffing_bytes; /* :2=01 ! scr:3 ! marker:1==1 ! scr:15 ! marker:1==1 ! scr:15 */ /* check markers */ if ((scr1 & 0xc4000400) != 0x44000400) goto beach; scr = ((guint64) scr1 & 0x38000000) << 3; scr |= ((guint64) scr1 & 0x03fff800) << 4; scr |= ((guint64) scr1 & 0x000003ff) << 5; scr |= ((guint64) scr2 & 0xf8000000) >> 27; /* marker:1==1 ! scr_ext:9 ! marker:1==1 */ if ((scr2 & 0x04010000) != 0x04010000) goto beach; scr_ext = (scr2 & 0x03fe0000) >> 17; if (scr_ext) { scr = (scr * 300 + scr_ext % 300) / 300; } /* SCR has been converted into units of 90Khz ticks to make it comparable to DTS/PTS, that also implies 1 tick rounding error */ data += 6; /* PMR:22 ! :2==11 ! reserved:5 ! stuffing_len:3 */ next32 = GST_READ_UINT32_BE (data); if ((next32 & 0x00000300) != 0x00000300) goto beach; stuffing_bytes = (next32 & 0x07); data += 4; while (stuffing_bytes--) { if (*data++ != 0xff) goto beach; } } else { /* check markers */ if ((scr1 & 0xf1000100) != 0x21000100) goto beach; if ((scr2 & 0x01800001) != 0x01800001) goto beach; /* :4=0010 ! scr:3 ! marker:1==1 ! scr:15 ! marker:1==1 ! scr:15 ! marker:1==1 */ scr = ((guint64) scr1 & 0x0e000000) << 5; scr |= ((guint64) scr1 & 0x00fffe00) << 6; scr |= ((guint64) scr1 & 0x000000ff) << 7; scr |= ((guint64) scr2 & 0xfe000000) >> 25; data += 8; } if (mode == SCAN_SCR) { *rts = scr; ret = TRUE; goto beach; } /* read the 4 bytes for the PES sync code */ code = GST_READ_UINT32_BE (data); if (!gst_ps_demux_is_pes_sync (code)) goto beach; switch (code) { case ID_PS_PROGRAM_STREAM_MAP: case ID_PRIVATE_STREAM_2: case ID_ECM_STREAM: case ID_EMM_STREAM: case ID_PROGRAM_STREAM_DIRECTORY: case ID_DSMCC_STREAM: case ID_ITU_TREC_H222_TYPE_E_STREAM: case ID_PADDING_STREAM: goto beach; default: break; } /* skip sync code and size */ data += 6; pts = dts = -1; /* stuffing bits, first two bits are '10' for mpeg2 pes so this code is * not triggered. */ while (TRUE) { if (*data != 0xff) break; data++; } /* STD buffer size, never for mpeg2 */ if ((*data & 0xc0) == 0x40) data += 2; /* PTS but no DTS, never for mpeg2 */ if ((*data & 0xf0) == 0x20) { READ_TS (data, pts, beach); } /* PTS and DTS, never for mpeg2 */ else if ((*data & 0xf0) == 0x30) { READ_TS (data, pts, beach); READ_TS (data, dts, beach); } else if ((*data & 0xc0) == 0x80) { /* mpeg2 case */ guchar flags; /* 2: '10' * 2: PES_scrambling_control * 1: PES_priority * 1: data_alignment_indicator * 1: copyright * 1: original_or_copy */ flags = *data++; if ((flags & 0xc0) != 0x80) goto beach; /* 2: PTS_DTS_flags * 1: ESCR_flag * 1: ES_rate_flag * 1: DSM_trick_mode_flag * 1: additional_copy_info_flag * 1: PES_CRC_flag * 1: PES_extension_flag */ flags = *data++; /* 8: PES_header_data_length */ data++; /* only DTS: this is invalid */ if ((flags & 0xc0) == 0x40) goto beach; /* check for PTS */ if ((flags & 0x80)) { READ_TS (data, pts, beach); } /* check for DTS */ if ((flags & 0x40)) { READ_TS (data, dts, beach); } } if (mode == SCAN_DTS && dts != (guint64) - 1) { *rts = dts; ret = TRUE; } if (mode == SCAN_PTS && pts != (guint64) - 1) { *rts = pts; ret = TRUE; } beach: return ret; } static inline gboolean gst_ps_demux_scan_forward_ts (GstPsDemux * demux, guint64 * pos, SCAN_MODE mode, guint64 * rts, gint limit) { GstFlowReturn ret = GST_FLOW_OK; GstBuffer *buffer; guint64 offset = *pos; gboolean found = FALSE; guint64 ts = 0; guint scan_sz = (mode == SCAN_SCR ? SCAN_SCR_SZ : SCAN_PTS_SZ); guint cursor, to_read = BLOCK_SZ; guint end_scan; GstMapInfo map; do { if (offset + scan_sz > demux->sink_segment.stop) return FALSE; if (limit && offset > *pos + limit) return FALSE; if (offset + to_read > demux->sink_segment.stop) to_read = demux->sink_segment.stop - offset; /* read some data */ buffer = NULL; ret = gst_pad_pull_range (demux->sinkpad, offset, to_read, &buffer); if (G_UNLIKELY (ret != GST_FLOW_OK)) return FALSE; gst_buffer_map (buffer, &map, GST_MAP_READ); /* may get a short buffer at the end of the file */ if (G_UNLIKELY (map.size <= scan_sz)) { gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); return FALSE; } end_scan = map.size - scan_sz; /* scan the block */ for (cursor = 0; !found && cursor <= end_scan; cursor++) { found = gst_ps_demux_scan_ts (demux, map.data + cursor, mode, &ts); } /* done with the buffer, unref it */ gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); if (found) { *rts = ts; *pos = offset + cursor - 1; } else { offset += cursor; } } while (!found && offset < demux->sink_segment.stop); return found; } static inline gboolean gst_ps_demux_scan_backward_ts (GstPsDemux * demux, guint64 * pos, SCAN_MODE mode, guint64 * rts, gint limit) { GstFlowReturn ret = GST_FLOW_OK; GstBuffer *buffer; guint64 offset = *pos; gboolean found = FALSE; guint64 ts = 0; guint scan_sz = (mode == SCAN_SCR ? SCAN_SCR_SZ : SCAN_PTS_SZ); guint cursor, to_read = BLOCK_SZ; guint start_scan; guint8 *data; GstMapInfo map; do { if (offset < scan_sz - 1) return FALSE; if (limit && offset < *pos - limit) return FALSE; if (offset > BLOCK_SZ) offset -= BLOCK_SZ; else { to_read = offset + 1; offset = 0; } /* read some data */ buffer = NULL; ret = gst_pad_pull_range (demux->sinkpad, offset, to_read, &buffer); if (G_UNLIKELY (ret != GST_FLOW_OK)) return FALSE; gst_buffer_map (buffer, &map, GST_MAP_READ); /* may get a short buffer at the end of the file */ if (G_UNLIKELY (map.size <= scan_sz)) { gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); return FALSE; } start_scan = map.size - scan_sz; data = map.data + start_scan; /* scan the block */ for (cursor = (start_scan + 1); !found && cursor > 0; cursor--) { found = gst_ps_demux_scan_ts (demux, data--, mode, &ts); } /* done with the buffer, unref it */ gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); if (found) { *rts = ts; *pos = offset + cursor; } } while (!found && offset > 0); return found; } static inline gboolean gst_ps_sink_get_duration (GstPsDemux * demux) { gboolean res = FALSE; GstPad *peer; GstFormat format = GST_FORMAT_BYTES; gint64 length = 0; guint64 offset; guint i; guint64 scr = 0; /* init the sink segment */ gst_segment_init (&demux->sink_segment, format); /* get peer to figure out length */ if ((peer = gst_pad_get_peer (demux->sinkpad)) == NULL) goto beach; res = gst_pad_query_duration (peer, format, &length); gst_object_unref (peer); if (!res || length <= 0) goto beach; GST_DEBUG_OBJECT (demux, "file length %" G_GINT64_FORMAT, length); /* update the sink segment */ demux->sink_segment.stop = length; gst_segment_set_duration (&demux->sink_segment, format, length); gst_segment_set_position (&demux->sink_segment, format, 0); /* Scan for notorious SCR and PTS to calculate the duration */ /* scan for first SCR in the stream */ offset = demux->sink_segment.start; gst_ps_demux_scan_forward_ts (demux, &offset, SCAN_SCR, &demux->first_scr, DURATION_SCAN_LIMIT); GST_DEBUG_OBJECT (demux, "First SCR: %" G_GINT64_FORMAT " %" GST_TIME_FORMAT " in packet starting at %" G_GUINT64_FORMAT, demux->first_scr, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->first_scr)), offset); demux->first_scr_offset = offset; /* scan for last SCR in the stream */ offset = demux->sink_segment.stop; gst_ps_demux_scan_backward_ts (demux, &offset, SCAN_SCR, &demux->last_scr, 0); GST_DEBUG_OBJECT (demux, "Last SCR: %" G_GINT64_FORMAT " %" GST_TIME_FORMAT " in packet starting at %" G_GUINT64_FORMAT, demux->last_scr, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->last_scr)), offset); demux->last_scr_offset = offset; /* scan for first PTS in the stream */ offset = demux->sink_segment.start; gst_ps_demux_scan_forward_ts (demux, &offset, SCAN_PTS, &demux->first_pts, DURATION_SCAN_LIMIT); GST_DEBUG_OBJECT (demux, "First PTS: %" G_GINT64_FORMAT " %" GST_TIME_FORMAT " in packet starting at %" G_GUINT64_FORMAT, demux->first_pts, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->first_pts)), offset); if (demux->first_pts != G_MAXUINT64) { /* scan for last PTS in the stream */ offset = demux->sink_segment.stop; gst_ps_demux_scan_backward_ts (demux, &offset, SCAN_PTS, &demux->last_pts, DURATION_SCAN_LIMIT); GST_DEBUG_OBJECT (demux, "Last PTS: %" G_GINT64_FORMAT " %" GST_TIME_FORMAT " in packet starting at %" G_GUINT64_FORMAT, demux->last_pts, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->last_pts)), offset); } /* Detect wrong SCR values */ if (demux->first_scr > demux->last_scr) { GST_DEBUG_OBJECT (demux, "Wrong SCR values detected, searching for " "a better first SCR value"); offset = demux->first_scr_offset; for (i = 0; i < 10; i++) { offset++; gst_ps_demux_scan_forward_ts (demux, &offset, SCAN_SCR, &scr, 0); if (scr < demux->last_scr) { demux->first_scr = scr; demux->first_scr_offset = offset; /* Start demuxing from the right place */ demux->sink_segment.position = offset; GST_DEBUG_OBJECT (demux, "Replaced First SCR: %" G_GINT64_FORMAT " %" GST_TIME_FORMAT " in packet starting at %" G_GUINT64_FORMAT, demux->first_scr, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->first_scr)), offset); break; } } } /* Set the base_time and avg rate */ demux->base_time = MPEGTIME_TO_GSTTIME (demux->first_scr); demux->scr_rate_n = demux->last_scr_offset - demux->first_scr_offset; demux->scr_rate_d = demux->last_scr - demux->first_scr; if (G_LIKELY (demux->first_pts != G_MAXUINT64 && demux->last_pts != G_MAXUINT64)) { /* update the src segment */ demux->src_segment.format = GST_FORMAT_TIME; demux->src_segment.start = MPEGTIME_TO_GSTTIME (demux->first_pts) - demux->base_time; demux->src_segment.stop = -1; gst_segment_set_duration (&demux->src_segment, GST_FORMAT_TIME, MPEGTIME_TO_GSTTIME (demux->last_pts - demux->first_pts)); gst_segment_set_position (&demux->src_segment, GST_FORMAT_TIME, demux->src_segment.start); } GST_INFO_OBJECT (demux, "sink segment configured %" GST_SEGMENT_FORMAT, &demux->sink_segment); GST_INFO_OBJECT (demux, "src segment configured %" GST_SEGMENT_FORMAT, &demux->src_segment); res = TRUE; beach: return res; } static inline GstFlowReturn gst_ps_demux_pull_block (GstPad * pad, GstPsDemux * demux, guint64 offset, guint size) { GstFlowReturn ret; GstBuffer *buffer = NULL; ret = gst_pad_pull_range (pad, offset, size, &buffer); if (G_UNLIKELY (ret != GST_FLOW_OK)) { GST_DEBUG_OBJECT (demux, "pull range at %" G_GUINT64_FORMAT " size %u failed", offset, size); goto beach; } else GST_LOG_OBJECT (demux, "pull range at %" G_GUINT64_FORMAT " size %u done", offset, size); if (demux->sink_segment.rate < 0) { GST_LOG_OBJECT (demux, "setting discont flag on backward rate"); GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); } ret = gst_ps_demux_chain (pad, GST_OBJECT (demux), buffer); beach: return ret; } static void gst_ps_demux_loop (GstPad * pad) { GstPsDemux *demux; GstFlowReturn ret = GST_FLOW_OK; guint64 offset = 0; demux = GST_PS_DEMUX (gst_pad_get_parent (pad)); if (G_UNLIKELY (demux->flushing)) { ret = GST_FLOW_FLUSHING; goto pause; } if (G_UNLIKELY (demux->sink_segment.format == GST_FORMAT_UNDEFINED)) gst_ps_sink_get_duration (demux); offset = demux->sink_segment.position; if (demux->sink_segment.rate >= 0) { guint size = BLOCK_SZ; if (G_LIKELY (demux->sink_segment.stop != (guint64) - 1)) { size = MIN (size, demux->sink_segment.stop - offset); } /* pull in data */ ret = gst_ps_demux_pull_block (pad, demux, offset, size); /* pause if something went wrong */ if (G_UNLIKELY (ret != GST_FLOW_OK)) goto pause; /* update our position */ offset += size; gst_segment_set_position (&demux->sink_segment, GST_FORMAT_BYTES, offset); /* check EOS condition */ if ((demux->src_segment.flags & GST_SEEK_FLAG_SEGMENT) && ((demux->sink_segment.position >= demux->sink_segment.stop) || (demux->src_segment.stop != (guint64) - 1 && demux->src_segment.position >= demux->src_segment.stop))) { GST_DEBUG_OBJECT (demux, "forward mode using segment reached end of " "segment pos %" GST_TIME_FORMAT " stop %" GST_TIME_FORMAT " pos in bytes %" G_GUINT64_FORMAT " stop in bytes %" G_GUINT64_FORMAT, GST_TIME_ARGS (demux->src_segment.position), GST_TIME_ARGS (demux->src_segment.stop), demux->sink_segment.position, demux->sink_segment.stop); ret = GST_FLOW_EOS; goto pause; } } else { /* Reverse playback */ guint64 size = MIN (offset, BLOCK_SZ); /* pull in data */ ret = gst_ps_demux_pull_block (pad, demux, offset - size, size); /* pause if something went wrong */ if (G_UNLIKELY (ret != GST_FLOW_OK)) goto pause; /* update our position */ offset -= size; gst_segment_set_position (&demux->sink_segment, GST_FORMAT_BYTES, offset); /* check EOS condition */ if (demux->sink_segment.position <= demux->sink_segment.start || demux->src_segment.position <= demux->src_segment.start) { GST_DEBUG_OBJECT (demux, "reverse mode using segment reached end of " "segment pos %" GST_TIME_FORMAT " stop %" GST_TIME_FORMAT " pos in bytes %" G_GUINT64_FORMAT " stop in bytes %" G_GUINT64_FORMAT, GST_TIME_ARGS (demux->src_segment.position), GST_TIME_ARGS (demux->src_segment.start), demux->sink_segment.position, demux->sink_segment.start); ret = GST_FLOW_EOS; goto pause; } } gst_object_unref (demux); return; pause: { const gchar *reason = gst_flow_get_name (ret); GST_LOG_OBJECT (demux, "pausing task, reason %s", reason); gst_pad_pause_task (pad); if (ret == GST_FLOW_EOS) { /* perform EOS logic */ gst_element_no_more_pads (GST_ELEMENT_CAST (demux)); if (demux->src_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->src_segment.stop) == -1) stop = demux->src_segment.duration; if (demux->sink_segment.rate >= 0) { GST_LOG_OBJECT (demux, "Sending segment done, at end of segment"); gst_element_post_message (GST_ELEMENT_CAST (demux), gst_message_new_segment_done (GST_OBJECT_CAST (demux), GST_FORMAT_TIME, stop)); gst_ps_demux_send_event (demux, gst_event_new_segment_done (GST_FORMAT_TIME, stop)); } else { /* Reverse playback */ GST_LOG_OBJECT (demux, "Sending segment done, at beginning of " "segment"); gst_element_post_message (GST_ELEMENT_CAST (demux), gst_message_new_segment_done (GST_OBJECT_CAST (demux), GST_FORMAT_TIME, demux->src_segment.start)); gst_ps_demux_send_event (demux, gst_event_new_segment_done (GST_FORMAT_TIME, demux->src_segment.start)); } } else { /* normal playback, send EOS to all linked pads */ gst_element_no_more_pads (GST_ELEMENT (demux)); GST_LOG_OBJECT (demux, "Sending EOS, at end of stream"); if (!gst_ps_demux_send_event (demux, gst_event_new_eos ()) && !have_open_streams (demux)) { GST_WARNING_OBJECT (demux, "EOS and no streams open"); GST_ELEMENT_ERROR (demux, STREAM, FAILED, ("Internal data stream error."), ("No valid streams detected")); } } } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { GST_ELEMENT_ERROR (demux, STREAM, FAILED, ("Internal data stream error."), ("stream stopped, reason %s", reason)); gst_ps_demux_send_event (demux, gst_event_new_eos ()); } gst_object_unref (demux); return; } } /* If we can pull that's prefered */ static gboolean gst_ps_demux_sink_activate (GstPad * sinkpad, GstObject * parent) { gboolean res = FALSE; GstQuery *query = gst_query_new_scheduling (); if (gst_pad_peer_query (sinkpad, query)) { if (gst_query_has_scheduling_mode_with_flags (query, GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE)) { res = gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE); } else { res = gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); } } else { res = gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); } gst_query_unref (query); return res; } /* This function gets called when we activate ourselves in push mode. */ static gboolean gst_ps_demux_sink_activate_push (GstPad * sinkpad, GstObject * parent, gboolean active) { GstPsDemux *demux = GST_PS_DEMUX (parent); demux->random_access = FALSE; return TRUE; } /* this function gets called when we activate ourselves in pull mode. * We can perform random access to the resource and we start a task * to start reading */ static gboolean gst_ps_demux_sink_activate_pull (GstPad * sinkpad, GstObject * parent, gboolean active) { GstPsDemux *demux = GST_PS_DEMUX (parent); if (active) { GST_DEBUG ("pull mode activated"); demux->random_access = TRUE; return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_ps_demux_loop, sinkpad, NULL); } else { demux->random_access = FALSE; return gst_pad_stop_task (sinkpad); } } static gboolean gst_ps_demux_sink_activate_mode (GstPad * pad, GstObject * parent, GstPadMode mode, gboolean active) { if (mode == GST_PAD_MODE_PUSH) { return gst_ps_demux_sink_activate_push (pad, parent, active); } else if (mode == GST_PAD_MODE_PULL) { return gst_ps_demux_sink_activate_pull (pad, parent, active); } return FALSE; } /* EOS and NOT_LINKED need to be combined. This means that we return: * * GST_FLOW_NOT_LINKED: when all pads NOT_LINKED. * GST_FLOW_EOS: when all pads EOS or NOT_LINKED. */ static GstFlowReturn gst_ps_demux_combine_flows (GstPsDemux * demux, GstFlowReturn ret) { GST_LOG_OBJECT (demux, "flow return: %s", gst_flow_get_name (ret)); ret = gst_flow_combiner_update_flow (demux->flowcombiner, ret); if (G_UNLIKELY (demux->need_no_more_pads && ret == GST_FLOW_NOT_LINKED)) ret = GST_FLOW_OK; GST_LOG_OBJECT (demux, "combined flow return: %s", gst_flow_get_name (ret)); return ret; } static GstFlowReturn gst_ps_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) { GstPsDemux *demux = GST_PS_DEMUX (parent); GstFlowReturn ret = GST_FLOW_OK; guint32 avail; gboolean save, discont; discont = GST_BUFFER_IS_DISCONT (buffer); if (discont) { GST_LOG_OBJECT (demux, "Received buffer with discont flag and" " offset %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buffer)); gst_pes_filter_drain (&demux->filter); gst_ps_demux_mark_discont (demux, TRUE, FALSE); /* mark discont on all streams */ if (demux->sink_segment.rate >= 0.0) { demux->current_scr = G_MAXUINT64; demux->bytes_since_scr = 0; } } else { GST_LOG_OBJECT (demux, "Received buffer with offset %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buffer)); } /* We keep the offset to interpolate SCR */ demux->adapter_offset = GST_BUFFER_OFFSET (buffer); gst_adapter_push (demux->adapter, buffer); demux->bytes_since_scr += gst_buffer_get_size (buffer); avail = gst_adapter_available (demux->rev_adapter); if (avail > 0) { GST_LOG_OBJECT (demux, "appending %u saved bytes", avail); /* if we have a previous reverse chunk, append this now */ /* FIXME this code assumes we receive discont buffers all thei * time */ gst_adapter_push (demux->adapter, gst_adapter_take_buffer (demux->rev_adapter, avail)); } avail = gst_adapter_available (demux->adapter); GST_LOG_OBJECT (demux, "avail now: %d, state %d", avail, demux->filter.state); switch (demux->filter.state) { case STATE_DATA_SKIP: case STATE_DATA_PUSH: ret = gst_pes_filter_process (&demux->filter); break; case STATE_HEADER_PARSE: break; default: break; } switch (ret) { case GST_FLOW_NEED_MORE_DATA: /* Go and get more data */ ret = GST_FLOW_OK; goto done; case GST_FLOW_LOST_SYNC: /* for FLOW_OK or lost-sync, carry onto resync */ ret = GST_FLOW_OK; break; case GST_FLOW_OK: break; default: /* Any other return value should be sent upstream immediately */ goto done; } /* align adapter data to sync boundary, we keep the data up to the next sync * point. */ save = TRUE; while (gst_ps_demux_resync (demux, save)) { gboolean ps_sync = TRUE; if (G_UNLIKELY (demux->flushing)) { ret = GST_FLOW_FLUSHING; goto done; } /* now switch on last synced byte */ switch (demux->last_sync_code) { case ID_PS_PACK_START_CODE: ret = gst_ps_demux_parse_pack_start (demux); break; case ID_PS_SYSTEM_HEADER_START_CODE: ret = gst_ps_demux_parse_sys_head (demux); break; case ID_PS_END_CODE: /* Skip final 4 bytes */ gst_adapter_flush (demux->adapter, 4); ADAPTER_OFFSET_FLUSH (4); ret = GST_FLOW_OK; goto done; case ID_PS_PROGRAM_STREAM_MAP: ret = gst_ps_demux_parse_psm (demux); break; default: if (gst_ps_demux_is_pes_sync (demux->last_sync_code)) { ret = gst_pes_filter_process (&demux->filter); } else { GST_DEBUG_OBJECT (demux, "sync_code=%08x, non PES sync found" ", continuing", demux->last_sync_code); ps_sync = FALSE; ret = GST_FLOW_LOST_SYNC; } break; } /* if we found a ps sync, we stop saving the data, any non-ps sync gets * saved up to the next ps sync. */ if (ps_sync) save = FALSE; switch (ret) { case GST_FLOW_NEED_MORE_DATA: GST_DEBUG_OBJECT (demux, "need more data"); ret = GST_FLOW_OK; goto done; case GST_FLOW_LOST_SYNC: if (!save || demux->sink_segment.rate >= 0.0) { GST_DEBUG_OBJECT (demux, "flushing 3 bytes"); gst_adapter_flush (demux->adapter, 3); ADAPTER_OFFSET_FLUSH (3); } else { GST_DEBUG_OBJECT (demux, "saving 3 bytes"); gst_adapter_push (demux->rev_adapter, gst_adapter_take_buffer (demux->adapter, 3)); } ret = GST_FLOW_OK; break; default: ret = gst_ps_demux_combine_flows (demux, ret); if (ret != GST_FLOW_OK) goto done; break; } } done: return ret; } static GstStateChangeReturn gst_ps_demux_change_state (GstElement * element, GstStateChange transition) { GstPsDemux *demux = GST_PS_DEMUX (element); GstStateChangeReturn result; switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: gst_pes_filter_init (&demux->filter, demux->adapter, &demux->adapter_offset); gst_pes_filter_set_callbacks (&demux->filter, (GstPESFilterData) gst_ps_demux_data_cb, (GstPESFilterResync) gst_ps_demux_resync_cb, demux); demux->filter.gather_pes = TRUE; break; case GST_STATE_CHANGE_READY_TO_PAUSED: break; default: break; } result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: gst_ps_demux_reset (demux); break; case GST_STATE_CHANGE_READY_TO_NULL: gst_pes_filter_uninit (&demux->filter); break; default: break; } return result; } static void gst_segment_set_position (GstSegment * segment, GstFormat format, guint64 position) { if (segment->format == GST_FORMAT_UNDEFINED) { segment->format = format; } segment->position = position; } static void gst_segment_set_duration (GstSegment * segment, GstFormat format, guint64 duration) { if (segment->format == GST_FORMAT_UNDEFINED) { segment->format = format; } segment->duration = duration; }