From 555486f865d6933da9cf983fc161e56474468ec4 Mon Sep 17 00:00:00 2001 From: Zaheer Abbas Merali Date: Tue, 2 Sep 2008 12:04:32 +0000 Subject: Add Fluendo MPEG PS and TS demuxers to gst-plugins-bad. This is now dual licensed MPL and LGPL. Original commit message from CVS: * configure.ac: * gst/mpegdemux/Makefile.am: * gst/mpegdemux/flumpegdemux.c: * gst/mpegdemux/flutspatinfo.c: * gst/mpegdemux/flutspatinfo.h: * gst/mpegdemux/flutspmtinfo.c: * gst/mpegdemux/flutspmtinfo.h: * gst/mpegdemux/flutspmtstreaminfo.c: * gst/mpegdemux/flutspmtstreaminfo.h: * gst/mpegdemux/gstmpegdefs.h: * gst/mpegdemux/gstmpegdemux.c: * gst/mpegdemux/gstmpegdemux.h: * gst/mpegdemux/gstmpegdesc.c: * gst/mpegdemux/gstmpegdesc.h: * gst/mpegdemux/gstmpegtsdemux.c: * gst/mpegdemux/gstmpegtsdemux.h: * gst/mpegdemux/gstpesfilter.c: * gst/mpegdemux/gstpesfilter.h: * gst/mpegdemux/gstsectionfilter.c: * gst/mpegdemux/gstsectionfilter.h: Add Fluendo MPEG PS and TS demuxers to gst-plugins-bad. This is now dual licensed MPL and LGPL. --- gst/mpegdemux/gstmpegdemux.c | 1869 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1869 insertions(+) create mode 100644 gst/mpegdemux/gstmpegdemux.c (limited to 'gst/mpegdemux/gstmpegdemux.c') diff --git a/gst/mpegdemux/gstmpegdemux.c b/gst/mpegdemux/gstmpegdemux.c new file mode 100644 index 00000000..a4b62d3c --- /dev/null +++ b/gst/mpegdemux/gstmpegdemux.c @@ -0,0 +1,1869 @@ + /* + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gstmpegdefs.h" +#include "gstmpegdemux.h" + +#define MAX_DVD_AUDIO_STREAMS 8 +#define MAX_DVD_SUBPICTURE_STREAMS 32 + +/* 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->first_scr)), 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) + +#ifndef GST_CHECK_VERSION +#define GST_CHECK_VERSION(major,minor,micro) \ + (GST_VERSION_MAJOR > (major) || \ + (GST_VERSION_MAJOR == (major) && GST_VERSION_MINOR > (minor)) || \ + (GST_VERSION_MAJOR == (major) && GST_VERSION_MINOR == (minor) && \ + GST_VERSION_MICRO >= (micro))) +#endif + +#if !GST_CHECK_VERSION(0,10,9) +#define GST_BUFFER_IS_DISCONT(buffer) (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) +#endif + +#if GST_CHECK_VERSION(0,10,6) +#define HAVE_NEWSEG_FULL +#else +static GstBuffer * +gst_adapter_take_buffer (GstAdapter * adapter, guint nbytes) +{ + GstBuffer *buf = NULL; + + if (G_UNLIKELY (nbytes > adapter->size)) + return NULL; + + buf = gst_buffer_new_and_alloc (nbytes); + + if (G_UNLIKELY (!buf)) + return NULL; + + /* Slow... */ + memcpy (GST_BUFFER_DATA (buf), gst_adapter_peek (adapter, nbytes), nbytes); + + return buf; +} +#endif + +/* elementfactory information */ +static GstElementDetails flups_demux_details = { + "MPEG Program Demuxer", + "Codec/Demuxer", + "Demultiplexes MPEG Program Streams", + "Wim Taymans " +}; + +#define GST_TYPE_FLUPS_DEMUX_SYNC (gst_flups_demux_sync_get_type ()) +GType +gst_flups_demux_sync_get_type (void) +{ + static GType sync_type = 0; + static GEnumValue sync_types[] = { + {GST_FLUPS_DEMUX_SYNC_AUTO, "Auto-select method to synchronize streams", + "auto"}, + {GST_FLUPS_DEMUX_SYNC_SCR, "Use SCR to synchronize streams", "scr"}, + {GST_FLUPS_DEMUX_SYNC_DTS, "Use DTS to synchronize streams", "dts"}, + {0, NULL, NULL} + }; + + if (sync_type == 0) + sync_type = g_enum_register_static ("GstFluPSDemuxSync", sync_types); + + return sync_type; +} + +/* 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;" + "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/x-private1-lpcm; " + "audio/x-private1-ac3;" "audio/x-private1-dts;" "audio/ac3") + ); + +static GstStaticPadTemplate private_template = +GST_STATIC_PAD_TEMPLATE ("private_%d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static void gst_flups_demux_base_init (GstFluPSDemuxClass * klass); +static void gst_flups_demux_class_init (GstFluPSDemuxClass * klass); +static void gst_flups_demux_init (GstFluPSDemux * demux); +static void gst_flups_demux_finalize (GstFluPSDemux * demux); +static void gst_flups_demux_reset (GstFluPSDemux * demux); + +static gboolean gst_flups_demux_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_flups_demux_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_flups_demux_src_query (GstPad * pad, GstQuery * query); +static GstFlowReturn gst_flups_demux_chain (GstPad * pad, GstBuffer * buffer); + +static GstStateChangeReturn gst_flups_demux_change_state (GstElement * element, + GstStateChange transition); + +static GstElementClass *parent_class = NULL; + +/*static guint gst_flups_demux_signals[LAST_SIGNAL] = { 0 };*/ + +GType +gst_flups_demux_get_type (void) +{ + static GType flups_demux_type = 0; + + if (!flups_demux_type) { + static const GTypeInfo flups_demux_info = { + sizeof (GstFluPSDemuxClass), + (GBaseInitFunc) gst_flups_demux_base_init, + NULL, + (GClassInitFunc) gst_flups_demux_class_init, + NULL, + NULL, + sizeof (GstFluPSDemux), + 0, + (GInstanceInitFunc) gst_flups_demux_init, + }; + + flups_demux_type = + g_type_register_static (GST_TYPE_ELEMENT, "GstFluPSDemux", + &flups_demux_info, 0); + + GST_DEBUG_CATEGORY_INIT (gstflupsdemux_debug, "flupsdemux", 0, + "MPEG program stream demultiplexer element"); + } + + return flups_demux_type; +} + +static void +gst_flups_demux_base_init (GstFluPSDemuxClass * 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->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->private_template); + gst_element_class_add_pad_template (element_class, klass->sink_template); + + gst_element_class_set_details (element_class, &flups_demux_details); +} + +static void +gst_flups_demux_class_init (GstFluPSDemuxClass * 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_flups_demux_finalize; + + gstelement_class->change_state = gst_flups_demux_change_state; +} + +static void +gst_flups_demux_init (GstFluPSDemux * demux) +{ + GstFluPSDemuxClass *klass = GST_FLUPS_DEMUX_GET_CLASS (demux); + + demux->sinkpad = gst_pad_new_from_template (klass->sink_template, "sink"); + gst_pad_set_event_function (demux->sinkpad, gst_flups_demux_sink_event); + gst_pad_set_chain_function (demux->sinkpad, gst_flups_demux_chain); + gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); + + demux->streams = + g_malloc0 (sizeof (GstFluPSStream *) * (GST_FLUPS_DEMUX_MAX_STREAMS)); + +} + +static void +gst_flups_demux_finalize (GstFluPSDemux * demux) +{ + gst_flups_demux_reset (demux); + g_free (demux->streams); + + G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT (demux)); +} + +static void +gst_flups_demux_reset (GstFluPSDemux * demux) +{ + /* Clean up the streams and pads we allocated */ + gint i; + GstEvent **p_ev; + + for (i = 0; i < GST_FLUPS_DEMUX_MAX_STREAMS; i++) { + GstFluPSStream *stream = demux->streams[i]; + + if (stream != NULL) { + if (stream->pad) + gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad); + + g_free (stream); + demux->streams[i] = NULL; + } + } + p_ev = &demux->lang_codes; + + gst_event_replace (p_ev, NULL); +} + +static GstFluPSStream * +gst_flups_demux_create_stream (GstFluPSDemux * demux, gint id, gint stream_type) +{ + GstFluPSStream *stream; + GstPadTemplate *template; + gchar *name; + GstFluPSDemuxClass *klass = GST_FLUPS_DEMUX_GET_CLASS (demux); + GstCaps *caps; + + 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, NULL); + 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: + case ST_AUDIO_AAC: + break; + case ST_VIDEO_H264: + template = klass->video_template; + name = g_strdup_printf ("video_%02x", id); + caps = gst_caps_new_simple ("video/x-h264", NULL); + break; + case ST_PS_AUDIO_AC3: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/x-private1-ac3", NULL); + break; + case ST_PS_AUDIO_DTS: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/x-private1-dts", NULL); + break; + case ST_PS_AUDIO_LPCM: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/x-private1-lpcm", NULL); + break; + case ST_PS_DVD_SUBPICTURE: + break; + case ST_GST_AUDIO_RAWA52: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/ac3", NULL); + break; + default: + break; + } + + if (name == NULL || template == NULL || caps == NULL) + return NULL; + + stream = g_new0 (GstFluPSStream, 1); + stream->id = id; + stream->discont = TRUE; + stream->need_segment = TRUE; + stream->notlinked = FALSE; + stream->type = stream_type; + stream->pad = gst_pad_new_from_template (template, name); + gst_pad_set_event_function (stream->pad, gst_flups_demux_src_event); + gst_pad_set_query_function (stream->pad, gst_flups_demux_src_query); + gst_pad_use_fixed_caps (stream->pad); + gst_pad_set_caps (stream->pad, caps); + gst_caps_unref (caps); + GST_DEBUG_OBJECT (demux, "create pad %s, caps %" GST_PTR_FORMAT, name, caps); + g_free (name); + + + return stream; +} + +static GstFluPSStream * +gst_flups_demux_get_stream (GstFluPSDemux * demux, gint id, gint type) +{ + GstFluPSStream *stream = demux->streams[id]; + + if (stream == NULL) { + if (!(stream = gst_flups_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); + + gst_pad_set_active (stream->pad, TRUE); + gst_element_add_pad (GST_ELEMENT (demux), stream->pad); + + demux->streams[id] = stream; + } + return stream; + + /* ERROR */ +unknown_stream: + { + GST_DEBUG_OBJECT (demux, "unknown stream id 0x%02x type 0x%02x", id, type); + return NULL; + } +} + +static GstFlowReturn +gst_flups_demux_send_data (GstFluPSDemux * demux, GstFluPSStream * stream, + GstBuffer * buf) +{ + GstFlowReturn result; + guint64 timestamp; + guint size; + + if (stream == NULL) + goto no_stream; + + /* discont */ + if (stream->need_segment) { + gint64 time, start, stop; + GstEvent *newsegment; + + start = demux->base_time + demux->src_segment.start; + if (demux->src_segment.stop != -1) + stop = demux->base_time + demux->src_segment.stop; + else + stop = -1; + time = start; + +#ifdef HAVE_NEWSEG_FULL + GST_INFO_OBJECT (demux, "sending new segment: rate %g applied_rate %g " + "start: %" GST_TIME_FORMAT ", stop: %" GST_TIME_FORMAT + ", time: %" GST_TIME_FORMAT " to pad %" GST_PTR_FORMAT, + demux->sink_segment.rate, demux->sink_segment.applied_rate, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (time), stream->pad); + + newsegment = gst_event_new_new_segment_full (FALSE, + demux->sink_segment.rate, demux->sink_segment.applied_rate, + GST_FORMAT_TIME, start, stop, time); +#else + GST_INFO_OBJECT (demux, "sending new segment: rate %g " + "start: %" GST_TIME_FORMAT ", stop: %" GST_TIME_FORMAT + ", time: %" GST_TIME_FORMAT " to pad %" GST_PTR_FORMAT, + demux->sink_segment.rate, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop), GST_TIME_ARGS (time), stream->pad); + + newsegment = gst_event_new_new_segment (FALSE, + demux->sink_segment.rate, GST_FORMAT_TIME, start, stop, time); +#endif + + gst_pad_push_event (stream->pad, newsegment); + + stream->need_segment = FALSE; + } + + /* timestamps */ + if (demux->next_pts != G_MAXUINT64) + timestamp = MPEGTIME_TO_GSTTIME (demux->next_pts); + else + timestamp = GST_CLOCK_TIME_NONE; + + /* OK, sent new segment now prepare the buffer for sending */ + /* caps */ + gst_buffer_set_caps (buf, GST_PAD_CAPS (stream->pad)); + GST_BUFFER_TIMESTAMP (buf) = timestamp; + + /* Set the buffer discont flag, and clear discont state on the stream */ + if (stream->discont) { + GST_DEBUG_OBJECT (demux, "marking discont buffer"); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + + stream->discont = FALSE; + } + size = GST_BUFFER_SIZE (buf); + + demux->next_pts = G_MAXUINT64; + demux->next_dts = G_MAXUINT64; + + result = gst_pad_push (stream->pad, buf); + GST_DEBUG_OBJECT (demux, "pushed stream id 0x%02x type 0x%02x, time: %" + GST_TIME_FORMAT ", size %d. result: %s", + stream->id, stream->type, GST_TIME_ARGS (timestamp), + size, 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 void +gst_flups_demux_mark_discont (GstFluPSDemux * demux, gboolean discont, + gboolean need_segment) +{ + gint id; + + /* mark discont on all streams */ + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream) { + stream->discont |= discont; + stream->need_segment |= need_segment; + GST_DEBUG_OBJECT (demux, "marked stream as discont %d, need_segment %d", + stream->discont, stream->need_segment); + } + } +} + +static gboolean +gst_flups_demux_send_event (GstFluPSDemux * demux, GstEvent * event) +{ + gint id; + gboolean ret = FALSE; + + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream && !stream->notlinked) { + (void) gst_event_ref (event); + + if (!gst_pad_push_event (stream->pad, event)) { + GST_DEBUG_OBJECT (stream, "event %s was not handled correctly", + GST_EVENT_TYPE_NAME (event)); + } else { + /* If at least one push returns TRUE, then we return TRUE. */ + GST_DEBUG_OBJECT (stream, "event %s was handled correctly", + GST_EVENT_TYPE_NAME (event)); + ret = TRUE; + } + } + } + + gst_event_unref (event); + return ret; +} + +static gboolean +gst_flups_demux_handle_dvd_event (GstFluPSDemux * 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]; + GstFluPSStream *temp; + + if (strcmp (type, "dvd-lang-codes") == 0) { + GstEvent **p_ev; + /* Store the language codes event on the element, then iterate over the + * streams it specifies and retrieve them. The stream creation code then + * creates the pad appropriately and sends tag events as needed */ + p_ev = &demux->lang_codes, event; + gst_event_replace (p_ev, event); + + GST_DEBUG_OBJECT (demux, "Handling language codes event"); + + /* Create a video pad to ensure have it before emit no more pads */ + temp = gst_flups_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)) + break; + + switch (stream_format) { + case 0x0: + /* AC3 */ + stream_id = 0x80 + i; + temp = gst_flups_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 + i; + temp = gst_flups_demux_get_stream (demux, stream_id, ST_AUDIO_MPEG1); + break; + case 0x4: + /* LPCM */ + stream_id = 0xA0 + i; + temp = + gst_flups_demux_get_stream (demux, stream_id, ST_PS_AUDIO_LPCM); + break; + case 0x6: + /* DTS */ + stream_id = 0x88 + i; + temp = gst_flups_demux_get_stream (demux, stream_id, ST_PS_AUDIO_DTS); + break; + case 0x7: + /* FIXME: What range is SDDS? */ + break; + default: + GST_WARNING_OBJECT (demux, + "Unknown audio stream format in language code event: %d", + stream_format); + break; + } + } + + /* And subtitle streams */ + for (i = 0; i < MAX_DVD_SUBPICTURE_STREAMS; i++) { + g_snprintf (cur_stream_name, 32, "subpicture-%d-format", i); + + if (gst_structure_get_string (structure, cur_stream_name) == NULL) + break; + + /* Retrieve the subpicture stream to force pad creation */ + temp = gst_flups_demux_get_stream (demux, 0x20 + i, ST_PS_DVD_SUBPICTURE); + } + + 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; + } + + gst_event_unref (event); + return TRUE; +} + +static gboolean +gst_flups_demux_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + GstFluPSDemux *demux; + + demux = GST_FLUPS_DEMUX (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + gst_flups_demux_send_event (demux, event); + break; + case GST_EVENT_FLUSH_STOP: + gst_flups_demux_send_event (demux, event); + + gst_segment_init (&demux->sink_segment, GST_FORMAT_UNDEFINED); + gst_adapter_clear (demux->adapter); + gst_adapter_clear (demux->rev_adapter); + demux->adapter_offset = G_MAXUINT64; + gst_pes_filter_drain (&demux->filter); + demux->current_scr = G_MAXUINT64; + demux->bytes_since_scr = 0; + break; + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + gdouble rate; + GstFormat format; + gint64 start, stop, time; + +#ifdef HAVE_NEWSEG_FULL + { + gdouble arate; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, + &format, &start, &stop, &time); + gst_segment_set_newsegment_full (&demux->sink_segment, update, rate, + arate, format, start, stop, time); + + if (format == GST_FORMAT_BYTES && demux->scr_rate_n != G_MAXUINT64 + && demux->scr_rate_d != G_MAXUINT64) { + + gst_segment_set_newsegment_full (&demux->src_segment, update, rate, + arate, GST_FORMAT_TIME, BYTES_TO_GSTTIME (start), + BYTES_TO_GSTTIME (stop), BYTES_TO_GSTTIME (time)); + } + + } +#else + gst_event_parse_new_segment (event, &update, &rate, &format, + &start, &stop, &time); + gst_segment_set_newsegment (&demux->sink_segment, update, rate, + format, start, stop, time); + if (format == GST_FORMAT_BYTES && demux->scr_rate_n != G_MAXUINT64 + && demux->scr_rate_d != G_MAXUINT64) { + + gst_segment_set_newsegment (&demux->src_segment, update, rate, + GST_FORMAT_TIME, BYTES_TO_GSTTIME (start), BYTES_TO_GSTTIME (stop), + BYTES_TO_GSTTIME (time)); + } +#endif + + GST_INFO_OBJECT (demux, "received new segment: rate %g " + "format %d, start: %" G_GINT64_FORMAT ", stop: %" G_GINT64_FORMAT + ", time: %" G_GINT64_FORMAT, rate, format, start, stop, time); + + /* we need to emit a new segment */ + gst_flups_demux_mark_discont (demux, TRUE, TRUE); + gst_event_unref (event); + + break; + } + case GST_EVENT_EOS: + GST_INFO_OBJECT (demux, "Received EOS"); + if (!gst_flups_demux_send_event (demux, event)) { + GST_WARNING_OBJECT (demux, "failed pushing EOS on streams"); + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + ("Internal data stream error."), ("Can't push EOS downstream")); + } + 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_flups_demux_handle_dvd_event (demux, event); + } else { + gst_flups_demux_send_event (demux, event); + } + break; + } + default: + gst_flups_demux_send_event (demux, event); + break; + } + + gst_object_unref (demux); + + return res; +} + +static gboolean +gst_flups_demux_src_event (GstPad * pad, GstEvent * event) +{ + gboolean res = FALSE; + GstFluPSDemux *demux; + + demux = GST_FLUPS_DEMUX (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + 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 (start); + bstop = GSTTIME_TO_BYTES (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); + break; + } + default: + res = gst_pad_push_event (demux->sinkpad, event); + break; + } + + gst_object_unref (demux); + + return res; + +not_supported: + { + gst_object_unref (demux); + gst_event_unref (event); + + return FALSE; + } +} + +static gboolean +gst_flups_demux_src_query (GstPad * pad, GstQuery * query) +{ + gboolean res = FALSE; + GstFluPSDemux *demux; + + demux = GST_FLUPS_DEMUX (gst_pad_get_parent (pad)); + + 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: + { + GstFormat format; + gint64 position; + + gst_query_parse_position (query, &format, NULL); + + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (demux, "position not supported for format %d", + format); + goto not_supported; + } + if (demux->current_scr == G_MAXUINT64 || demux->first_scr == G_MAXUINT64) { + GST_DEBUG_OBJECT (demux, "position not possible, no current_scr"); + goto not_supported; + } + + position = demux->base_time + + MPEGTIME_TO_GSTTIME (demux->current_scr - demux->first_scr); + + GST_LOG_OBJECT (demux, "Position at GStreamer Time:%" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + gst_query_set_position (query, format, position); + res = TRUE; + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + gint64 duration; + GstPad *peer; + + gst_query_parse_duration (query, &format, NULL); + + if ((peer = gst_pad_get_peer (demux->sinkpad)) == NULL) { + GST_DEBUG_OBJECT (demux, "duration not possible, no peer"); + goto not_supported; + } + + /* 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 %d", + format); + gst_object_unref (peer); + goto not_supported; + } + + if (gst_pad_query (peer, query)) { + gst_object_unref (peer); + 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 %d", + format); + gst_object_unref (peer); + goto not_supported; + } + + if (demux->mux_rate == -1) { + GST_DEBUG_OBJECT (demux, "duration not possible, no mux_rate"); + gst_object_unref (peer); + goto not_supported; + } + + gst_query_set_duration (query, GST_FORMAT_BYTES, -1); + + if (!gst_pad_query (peer, query)) { + GST_LOG_OBJECT (demux, "query on peer pad failed"); + gst_object_unref (peer); + goto not_supported; + } + gst_object_unref (peer); + + gst_query_parse_duration (query, &format, &duration); + + duration = BYTES_TO_GSTTIME (duration); + + gst_query_set_duration (query, GST_FORMAT_TIME, duration); + res = TRUE; + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + + gst_object_unref (demux); + + return res; + +not_supported: + { + gst_object_unref (demux); + + return FALSE; + } +} + +static void +gst_flups_demux_reset_psm (GstFluPSDemux * demux) +{ + gint i; + +#define FILL_TYPE(start, stop, type) \ + for (i=start; i <= stop; i++) \ + demux->psm[i] = type; + + FILL_TYPE (0x00, 0x1f, -1); + FILL_TYPE (0x20, 0x3f, ST_PS_DVD_SUBPICTURE); + FILL_TYPE (0x40, 0x7f, -1); + FILL_TYPE (0x80, 0x87, ST_PS_AUDIO_AC3); + FILL_TYPE (0x88, 0x9f, ST_PS_AUDIO_DTS); + FILL_TYPE (0xa0, 0xbf, ST_PS_AUDIO_LPCM); + FILL_TYPE (0xbd, 0xbd, -1); + FILL_TYPE (0xc0, 0xdf, ST_AUDIO_MPEG1); + FILL_TYPE (0xe0, 0xef, ST_GST_VIDEO_MPEG1_OR_2); + FILL_TYPE (0xf0, 0xff, -1); + +#undef FILL_TYPE +} + +static GstFlowReturn +gst_flups_demux_parse_pack_start (GstFluPSDemux * demux) +{ + const guint8 *data; + guint length; + guint32 scr1, scr2; + guint64 scr, scr_adjusted, new_rate; + + GST_DEBUG ("parsing pack start"); + + /* fixed length to begin with, start code and two scr values */ + length = 8 + 4; + + if (!(data = gst_adapter_peek (demux->adapter, length))) + goto need_more_data; + + /* skip start code */ + data += 4; + + scr1 = GUINT32_FROM_BE (*(guint32 *) data); + scr2 = GUINT32_FROM_BE (*(guint32 *) (data + 4)); + + /* start parsing the stream */ + if ((*data & 0xc0) == 0x40) { + guint32 scr_ext; + guint32 next32; + guint8 stuffing_bytes; + + GST_DEBUG ("Found MPEG2 stream"); + demux->is_mpeg2_pack = TRUE; + + /* mpeg2 has more data */ + length += 2; + if (gst_adapter_available (demux->adapter) < length) + goto need_more_data; + + /* :2=01 ! scr:3 ! marker:1==1 ! scr:15 ! marker:1==1 ! scr:15 */ + + /* check markers */ + if ((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 ((scr2 & 0x04010000) != 0x04010000) + goto lost_sync; + + scr_ext = (scr2 & 0x03fe0000) >> 17; + /* We keep the offset of this scr */ + demux->last_scr_offset = demux->adapter_offset + 12; + + GST_DEBUG_OBJECT (demux, "SCR: 0x%08x 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 = (GUINT32_FROM_BE ((*(guint32 *) data))); + if ((next32 & 0x00000300) != 0x00000300) + goto lost_sync; + + new_rate = (next32 & 0xfffffc00) >> 10; + + stuffing_bytes = (next32 & 0x07); + GST_DEBUG_OBJECT (demux, "stuffing bytes: %d", stuffing_bytes); + + data += 4; + while (stuffing_bytes--) { + if (*data++ != 0xff) + goto lost_sync; + } + } else { + GST_DEBUG ("Found MPEG1 stream"); + demux->is_mpeg2_pack = FALSE; + + /* check markers */ + if ((scr1 & 0xf1000100) != 0x21000100) + goto lost_sync; + + if ((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->last_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 - demux->first_scr))); + + /* keep the first src in order to calculate delta time */ + if (demux->first_scr == G_MAXUINT64) { + demux->first_scr = scr; + demux->first_scr_offset = demux->last_scr_offset; + demux->base_time = MPEGTIME_TO_GSTTIME (demux->first_scr); + /* at begin consider the new_rate as the scr rate, bytes/clock ticks */ + demux->scr_rate_n = new_rate; + demux->scr_rate_d = CLOCK_FREQ; + } else if (demux->first_scr_offset != demux->last_scr_offset) { + /* estimate byte rate related to the SCR */ + demux->scr_rate_n = demux->last_scr_offset - demux->first_scr_offset; + demux->scr_rate_d = scr - demux->first_scr; + } + + GST_DEBUG_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->last_scr_offset, + demux->first_scr, demux->first_scr_offset, + demux->scr_rate_n, demux->scr_rate_d, + (float) demux->scr_rate_n / demux->scr_rate_d); + + /* adjustment of the SCR */ + if (demux->current_scr != G_MAXUINT64) { + gint64 diff; + guint64 old_scr, old_mux_rate, bss, adjust; + + /* 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 - + gst_adapter_available (demux->adapter))); + + /* estimate the new SCR using the previous one according the notes + on point 2.5.2.2 of the ISO/IEC 13818-1 document */ + 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_DEBUG_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 (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 (diff > CLOCK_FREQ) { + demux->scr_adjust = demux->next_scr - scr; + GST_DEBUG_OBJECT (demux, "discont found, diff: %" G_GINT64_FORMAT + ", adjust %" G_GINT64_FORMAT, diff, demux->scr_adjust); + scr_adjusted = demux->next_scr; + } else { + demux->next_scr = scr_adjusted; + } + } + + /* update the current_scr and rate members */ + demux->mux_rate = new_rate; + demux->current_scr = scr_adjusted; + + /* Reset the bytes_since_scr value to count the data remaining in the + * adapter */ + demux->bytes_since_scr = gst_adapter_available (demux->adapter); + + gst_adapter_flush (demux->adapter, length); + ADAPTER_OFFSET_FLUSH (length); + return GST_FLOW_OK; + +lost_sync: + { + GST_DEBUG_OBJECT (demux, "lost sync"); + return GST_FLOW_LOST_SYNC; + } +need_more_data: + { + GST_DEBUG_OBJECT (demux, "need more data"); + return GST_FLOW_NEED_MORE_DATA; + } +} + +static GstFlowReturn +gst_flups_demux_parse_sys_head (GstFluPSDemux * demux) +{ + guint16 length; + const guint8 *data; + gboolean csps; + + /* start code + length */ + if (!(data = gst_adapter_peek (demux->adapter, 6))) + goto need_more_data; + + /* skip start code */ + data += 4; + + length = GST_READ_UINT16_BE (data); + GST_DEBUG_OBJECT (demux, "length %d", length); + + length += 6; + + if (!(data = gst_adapter_peek (demux->adapter, length))) + goto need_more_data; + + /* 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 */ + { + 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); + data += 1; + } + + /* audio_lock:1 | video_lock:1 | marker:1==1 | video_bound:5 */ + { + gboolean audio_lock; + gboolean video_lock; + guint8 video_bound; + + audio_lock = (data[0] & 0x80) == 0x80; + video_lock = (data[0] & 0x40) == 0x40; + + if ((data[0] & 0x20) != 0x20) + goto marker_expected; + + /* 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); + data += 1; + } + + /* packet_rate_restriction:1 | reserved:7==0x7F */ + { + gboolean packet_rate_restriction; + + if ((data[0] & 0x7f) != 0x7f) + goto marker_expected; + + /* 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); + } + } + 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; + gboolean STD_buffer_bound_scale; + guint16 STD_buffer_size_bound; + guint32 buf_byte_size_bound; + + stream_id = *data++; + if (!(stream_id & 0x80)) + goto sys_len_error; + + /* check marker bits */ + if ((*data & 0xC0) != 0xC0) + goto no_placeholder_bits; + + 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); + } + } + + gst_adapter_flush (demux->adapter, length); + ADAPTER_OFFSET_FLUSH (length); + return GST_FLOW_OK; + + /* ERRORS */ +marker_expected: + { + GST_DEBUG_OBJECT (demux, "expecting marker"); + return GST_FLOW_LOST_SYNC; + } +no_placeholder_bits: + { + GST_DEBUG_OBJECT (demux, "expecting placeholder bit values" + " '11' after stream id"); + return GST_FLOW_LOST_SYNC; + } +sys_len_error: + { + GST_DEBUG_OBJECT (demux, "error in system header length"); + return GST_FLOW_LOST_SYNC; + } +need_more_data: + { + GST_DEBUG_OBJECT (demux, "need more data"); + return GST_FLOW_NEED_MORE_DATA; + } +} + +static GstFlowReturn +gst_flups_demux_parse_psm (GstFluPSDemux * demux) +{ + guint16 length = 0, info_length = 0, es_map_length = 0; + guint8 psm_version = 0; + const guint8 *data, *es_map_base; + gboolean applicable; + + /* start code + length */ + if (!(data = gst_adapter_peek (demux->adapter, 6))) + goto need_more_data; + + /* 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; + + if (!(data = gst_adapter_peek (demux->adapter, length))) + goto need_more_data; + + /* skip start code and length */ + data += 6; + + /* Read PSM applicable bit together with version */ + psm_version = GST_READ_UINT8 (data); + applicable = (psm_version & 0x80) >> 7; + 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); + demux->psm[stream_id] = stream_type; + es_map_base += stream_info_length; + } + + 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"); + return GST_FLOW_LOST_SYNC; + } +need_more_data: + { + GST_DEBUG_OBJECT (demux, "need more data"); + return GST_FLOW_NEED_MORE_DATA; + } +} + +static void +gst_flups_demux_resync_cb (GstPESFilter * filter, GstFluPSDemux * demux) +{ +} + +static GstFlowReturn +gst_flups_demux_data_cb (GstPESFilter * filter, gboolean first, + GstBuffer * buffer, GstFluPSDemux * demux) +{ + GstBuffer *out_buf; + GstFlowReturn ret = GST_FLOW_OK; + gint stream_type; + guint32 start_code; + guint8 id; + guint8 *data; + guint datalen; + guint offset = 0; + + data = GST_BUFFER_DATA (buffer); + datalen = GST_BUFFER_SIZE (buffer); + + 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) { + guint8 nframes; + + /* 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 (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 */ + id = data[offset++]; + /* Number of audio frames in this packet */ + nframes = data[offset++]; + + GST_DEBUG_OBJECT (demux, "private type 0x%02x, %d frames", id, + nframes); + + datalen -= 2; + + /* and remap */ + stream_type = demux->psm[id]; + } + } + if (stream_type == -1) + goto unknown_stream_type; + } + if (filter->pts != -1) { + demux->next_pts = filter->pts + demux->scr_adjust; + GST_DEBUG_OBJECT (demux, "PTS = %" G_GUINT64_FORMAT + "(%" G_GUINT64_FORMAT ")", filter->pts, demux->next_pts); + } else + demux->next_pts = G_MAXUINT64; + + if (filter->dts != -1) { + demux->next_dts = filter->dts + demux->scr_adjust; + } else { + demux->next_dts = demux->next_pts; + } + GST_DEBUG_OBJECT (demux, "DTS = orig %" G_GUINT64_FORMAT + " (%" G_GUINT64_FORMAT ")", filter->dts, demux->next_dts); + + demux->current_stream = gst_flups_demux_get_stream (demux, id, stream_type); + } + + if (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_create_sub (buffer, offset, datalen); + + ret = gst_flups_demux_send_data (demux, demux->current_stream, out_buf); + if (ret == GST_FLOW_NOT_LINKED) { + demux->current_stream->notlinked = TRUE; + ret = GST_FLOW_OK; + } + } + +done: + 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_flups_demux_resync (GstFluPSDemux * demux, gboolean save) +{ + const guint8 *data; + gint avail; + guint32 code; + gint offset; + gboolean found; + + avail = gst_adapter_available (demux->adapter); + if (avail < 4) + goto need_data; + + /* Common case, read 4 bytes an check it */ + data = gst_adapter_peek (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; + 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_peek (demux->adapter, avail); + + do { + code = (code << 8) | data[offset++]; + found = (code & 0xffffff00) == 0x100L; + } while (offset < avail && !found); + + 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 gboolean +gst_flups_demux_is_pes_sync (guint32 sync) +{ + return ((sync & 0xfc) == 0xbc) || + ((sync & 0xe0) == 0xc0) || ((sync & 0xf0) == 0xe0); +} + +static GstFlowReturn +gst_flups_demux_chain (GstPad * pad, GstBuffer * buffer) +{ + GstFluPSDemux *demux = GST_FLUPS_DEMUX (gst_pad_get_parent (pad)); + 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_flups_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_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_flups_demux_resync (demux, save)) { + gboolean ps_sync = TRUE; + + /* now switch on last synced byte */ + switch (demux->last_sync_code) { + case ID_PS_PACK_START_CODE: + ret = gst_flups_demux_parse_pack_start (demux); + break; + case ID_PS_SYSTEM_HEADER_START_CODE: + ret = gst_flups_demux_parse_sys_head (demux); + break; + case ID_PS_END_CODE: + ret = GST_FLOW_OK; + goto done; + case ID_PS_PROGRAM_STREAM_MAP: + ret = gst_flups_demux_parse_psm (demux); + break; + default: + if (gst_flups_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: + break; + } + } +done: + gst_object_unref (demux); + + return ret; +} + +static GstStateChangeReturn +gst_flups_demux_change_state (GstElement * element, GstStateChange transition) +{ + GstFluPSDemux *demux = GST_FLUPS_DEMUX (element); + GstStateChangeReturn result; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + demux->adapter = gst_adapter_new (); + demux->rev_adapter = gst_adapter_new (); + demux->adapter_offset = G_MAXUINT64; + gst_pes_filter_init (&demux->filter, demux->adapter, + &demux->adapter_offset); + gst_pes_filter_set_callbacks (&demux->filter, + (GstPESFilterData) gst_flups_demux_data_cb, + (GstPESFilterResync) gst_flups_demux_resync_cb, demux); + demux->filter.gather_pes = TRUE; + demux->first_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; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + demux->current_scr = G_MAXUINT64; + demux->mux_rate = G_MAXUINT64; + demux->next_pts = G_MAXUINT64; + demux->next_dts = G_MAXUINT64; + demux->first_scr = G_MAXUINT64; + demux->base_time = G_MAXUINT64; + demux->scr_rate_n = G_MAXUINT64; + demux->scr_rate_d = G_MAXUINT64; + demux->need_no_more_pads = TRUE; + + gst_flups_demux_reset_psm (demux); + gst_segment_init (&demux->sink_segment, GST_FORMAT_UNDEFINED); + gst_segment_init (&demux->src_segment, GST_FORMAT_TIME); + break; + default: + break; + } + + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_flups_demux_reset (demux); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_pes_filter_uninit (&demux->filter); + g_object_unref (demux->adapter); + demux->adapter = NULL; + g_object_unref (demux->rev_adapter); + demux->rev_adapter = NULL; + break; + default: + break; + } + + return result; +} + +gboolean +gst_flups_demux_plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "flupsdemux", + GST_RANK_PRIMARY, GST_TYPE_FLUPS_DEMUX)) + return FALSE; + + return TRUE; +} -- cgit v1.2.1