/* * 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. * * 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 "gstmpegdefs.h" #include "gstmpegdemux.h" #define SEGMENT_THRESHOLD (300*GST_MSECOND) #define VIDEO_SEGMENT_THRESHOLD (500*GST_MSECOND) /* The SCR_MUNGE value is used to offset the scr_adjust value, to avoid * ever generating a negative timestamp */ #define SCR_MUNGE (10 * GST_SECOND) /* 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) GST_DEBUG_CATEGORY_EXTERN (mpegpspesfilter_debug); /* MPEG2Demux signals and args */ enum { /* FILL ME */ LAST_SIGNAL }; enum { ARG_0, /* FILL ME */ }; static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("application/x-resin-dvd") ); 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 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_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, GstObject * parent, GstEvent * event); static GstFlowReturn gst_flups_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer); static gboolean gst_flups_demux_sink_activate (GstPad * sinkpad, GstObject * parent); static gboolean gst_flups_demux_sink_activate_mode (GstPad * pad, GstObject * parent, GstPadMode mode, gboolean active); // static void gst_flups_demux_loop (GstPad * pad); static gboolean gst_flups_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_flups_demux_src_query (GstPad * pad, GstObject * parent, GstQuery * query); static GstStateChangeReturn gst_flups_demux_change_state (GstElement * element, GstStateChange transition); static inline void gst_flups_demux_send_gap_updates (GstFluPSDemux * demux, GstClockTime new_time); static inline void gst_flups_demux_clear_times (GstFluPSDemux * 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_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, NULL }; flups_demux_type = g_type_register_static (GST_TYPE_ELEMENT, "RsnDVDDemux", &flups_demux_info, 0); GST_DEBUG_CATEGORY_INIT (gstflupsdemux_debug, "rsndvddemux", 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->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_metadata (element_class, "MPEG Program Demuxer", "Codec/Demuxer", "Demultiplexes MPEG Program Streams", "Jan Schmidt "); } 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_DEBUG_FUNCPTR (gst_flups_demux_sink_event)); gst_pad_set_chain_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_flups_demux_chain)); gst_pad_set_activate_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_flups_demux_sink_activate)); gst_pad_set_activatemode_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_flups_demux_sink_activate_mode)); gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); demux->streams = g_malloc0 (sizeof (GstFluPSStream *) * (GST_FLUPS_DEMUX_MAX_STREAMS)); demux->scr_adjust = GSTTIME_TO_MPEGTIME (SCR_MUNGE); } 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); demux->scr_adjust = GSTTIME_TO_MPEGTIME (SCR_MUNGE); } 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; GstClockTime threshold = SEGMENT_THRESHOLD; 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); 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 (GstFluPSStream, 1); stream->id = id; stream->discont = TRUE; stream->notlinked = FALSE; stream->type = stream_type; stream->pad = gst_pad_new_from_template (template, name); stream->segment_thresh = threshold; gst_pad_set_event_function (stream->pad, GST_DEBUG_FUNCPTR (gst_flups_demux_src_event)); gst_pad_set_query_function (stream->pad, GST_DEBUG_FUNCPTR (gst_flups_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); } gst_pad_set_caps (stream->pad, caps); GST_DEBUG_OBJECT (demux, "create pad %s, caps %" GST_PTR_FORMAT, name, caps); gst_caps_unref (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 && !demux->disable_stream_creation) { 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_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; GstClockTime pts = GST_CLOCK_TIME_NONE, dts = GST_CLOCK_TIME_NONE; guint size; 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); if (demux->current_scr != G_MAXUINT64) { GstClockTime cur_scr_time = MPEGTIME_TO_GSTTIME (demux->current_scr); if (stream->last_ts == GST_CLOCK_TIME_NONE || stream->last_ts < cur_scr_time) { #if 0 g_print ("last_ts update on pad %s to time %" GST_TIME_FORMAT "\n", GST_PAD_NAME (stream->pad), GST_TIME_ARGS (cur_scr_time)); #endif stream->last_ts = cur_scr_time; } } /* OK, sent new segment now prepare the buffer for sending */ GST_BUFFER_PTS (buf) = pts; GST_BUFFER_DTS (buf) = dts; /* 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; } size = gst_buffer_get_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, pts time: %" GST_TIME_FORMAT ", size %d. result: %s", stream->id, stream->type, GST_TIME_ARGS (pts), 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 inline void gst_flups_demux_mark_discont (GstFluPSDemux * demux) { 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 = TRUE; GST_DEBUG_OBJECT (demux, "marked stream as discont %d", stream->discont); } } } static inline 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) { (void) gst_event_ref (event); if (!gst_pad_push_event (stream->pad, event)) { GST_DEBUG_OBJECT (stream, "event %s was not handled correctly by pad %" GST_PTR_FORMAT, GST_EVENT_TYPE_NAME (event), stream->pad); } else { /* If at least one push returns TRUE, then we return TRUE. */ GST_DEBUG_OBJECT (stream, "event %s was handled correctly by pad %" GST_PTR_FORMAT, GST_EVENT_TYPE_NAME (event), stream->pad); 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; gboolean ret = TRUE; 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; gst_event_replace (p_ev, event); GST_DEBUG_OBJECT (demux, "Handling language codes event"); demux->disable_stream_creation = FALSE; /* Create a video pad to ensure it exists before emitting no more pads */ temp = gst_flups_demux_get_stream (demux, 0xe0, ST_VIDEO_MPEG2); /* Send a video format event downstream */ { gboolean is_widescreen, is_pal; if (gst_structure_get_boolean (structure, "video-widescreen", &is_widescreen) && gst_structure_get_boolean (structure, "video-pal-format", &is_pal)) { GstEvent *v_format; GstStructure *v_struct; v_struct = gst_structure_new ("application/x-gst-dvd", "event", G_TYPE_STRING, "dvd-video-format", "video-widescreen", G_TYPE_BOOLEAN, is_widescreen, "video-pal-format", G_TYPE_BOOLEAN, is_pal, NULL); v_format = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, v_struct); gst_pad_push_event (temp->pad, v_format); } } /* 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; demux->audio_stream_types[i] = stream_format; 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_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; GST_DEBUG_OBJECT (demux, "Audio stream %d format %d ID 0x%02x - MPEG audio", i, stream_format, stream_id); temp = gst_flups_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_flups_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_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++) { gint stream_format; gint stream_id; g_snprintf (cur_stream_name, 32, "subpicture-%d-format", i); if (!gst_structure_get_int (structure, cur_stream_name, &stream_format)) 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 + i); /* Retrieve the subpicture stream to force pad creation */ temp = gst_flups_demux_get_stream (demux, 0x20 + stream_id, ST_PS_DVD_SUBPICTURE); } demux->disable_stream_creation = TRUE; 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); } else if (strcmp (type, "dvd-set-subpicture-track") == 0) { gint stream_id; gboolean forced_only; gst_structure_get_boolean (structure, "forced-only", &forced_only); if (gst_structure_get_int (structure, "physical-id", &stream_id)) { temp = demux->streams[0x20 + stream_id]; if (temp != NULL && temp->pad != NULL) { /* Send event to the selector to activate the desired pad */ GstStructure *s = gst_structure_new ("application/x-gst-dvd", "event", G_TYPE_STRING, "select-pad", NULL); GstEvent *sel_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); temp->notlinked = FALSE; gst_pad_push_event (temp->pad, sel_event); gst_event_ref (event); ret = gst_pad_push_event (temp->pad, event); GST_INFO_OBJECT (demux, "Subpicture physical ID change to %d, forced %d", stream_id, forced_only); } } gst_event_unref (event); } else if (strcmp (type, "dvd-set-audio-track") == 0) { gint stream_id; if (gst_structure_get_int (structure, "physical-id", &stream_id)) { gint aud_type; stream_id %= MAX_DVD_AUDIO_STREAMS; aud_type = demux->audio_stream_types[stream_id]; switch (aud_type) { case 0x0: /* AC3 */ stream_id += 0x80; temp = demux->streams[stream_id]; break; case 0x2: case 0x3: /* MPEG audio without and with extension stream are * treated the same */ stream_id += 0xC0; temp = demux->streams[stream_id]; break; case 0x4: /* LPCM */ stream_id += 0xA0; temp = demux->streams[stream_id]; break; case 0x6: /* DTS */ stream_id += 0x88; temp = demux->streams[stream_id]; break; case 0x7: /* FIXME: What range is SDDS? */ temp = NULL; break; default: temp = NULL; break; } GST_INFO_OBJECT (demux, "Have DVD audio stream select event: " "stream 0x%02x", stream_id); if (temp != NULL && temp->pad != NULL) { /* Send event to the selector to activate the desired pad */ GstStructure *s = gst_structure_new ("application/x-gst-dvd", "event", G_TYPE_STRING, "select-pad", NULL); GstEvent *sel_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); gst_pad_push_event (temp->pad, sel_event); gst_event_ref (event); ret = gst_pad_push_event (temp->pad, event); } } ret = gst_flups_demux_send_event (demux, event); } else { ret = gst_flups_demux_send_event (demux, event); } return ret; } static void gst_flups_demux_flush (GstFluPSDemux * demux) { GST_DEBUG_OBJECT (demux, "flushing demuxer"); gst_segment_init (&demux->src_segment, GST_FORMAT_TIME); gst_adapter_clear (demux->adapter); gst_adapter_clear (demux->rev_adapter); gst_pes_filter_drain (&demux->filter); gst_flups_demux_clear_times (demux); demux->adapter_offset = G_MAXUINT64; demux->current_scr = G_MAXUINT64; demux->bytes_since_scr = 0; demux->scr_adjust = GSTTIME_TO_MPEGTIME (SCR_MUNGE); demux->in_still = FALSE; } static inline void gst_flups_demux_clear_times (GstFluPSDemux * demux) { gint id; /* Clear the last ts for all streams */ for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { GstFluPSStream *stream = demux->streams[id]; if (stream) { stream->last_seg_start = stream->last_ts = GST_CLOCK_TIME_NONE; } } } static inline void gst_flups_demux_send_gap_updates (GstFluPSDemux * demux, GstClockTime new_time) { gint id; GstEvent *event = NULL; /* Advance all lagging streams by sending a segment update */ if (new_time > demux->src_segment.stop) return; for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { GstFluPSStream *stream = demux->streams[id]; if (stream) { if (stream->last_ts == GST_CLOCK_TIME_NONE || stream->last_ts < demux->src_segment.start) stream->last_ts = demux->src_segment.start; if (stream->last_ts + stream->segment_thresh < new_time) { #if 0 g_print ("Gap event update to pad %s from time %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n", GST_PAD_NAME (stream->pad), GST_TIME_ARGS (stream->last_ts), GST_TIME_ARGS (new_time)); #endif GST_DEBUG_OBJECT (demux, "Gap event update to pad %s from time %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, GST_PAD_NAME (stream->pad), GST_TIME_ARGS (stream->last_ts), GST_TIME_ARGS (new_time)); event = gst_event_new_gap (stream->last_ts, new_time - stream->last_ts); gst_pad_push_event (stream->pad, event); stream->last_seg_start = stream->last_ts = new_time; } } } } static gboolean gst_flups_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean res = TRUE; GstFluPSDemux *demux = GST_FLUPS_DEMUX (parent); 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_flups_demux_flush (demux); break; case GST_EVENT_SEGMENT: { const GstSegment *segment; gint64 start, stop, time; gint64 base, dur, position; GstClockTimeDiff adjust; gst_event_parse_segment (event, &segment); if (segment->format != GST_FORMAT_TIME) return FALSE; gst_segment_copy_into (segment, &demux->sink_segment); dur = segment->stop - segment->start; base = demux->sink_segment.base; start = demux->sink_segment.start; stop = demux->sink_segment.stop; time = demux->sink_segment.time; demux->first_scr = GSTTIME_TO_MPEGTIME (start); demux->current_scr = demux->first_scr + demux->scr_adjust; demux->base_time = time; demux->bytes_since_scr = 0; GST_DEBUG_OBJECT (demux, "demux: received new segment start %" G_GINT64_FORMAT " stop %" G_GINT64_FORMAT " time %" G_GINT64_FORMAT " base %" G_GINT64_FORMAT, start, stop, time, base); #if 0 g_print ("demux: received new segment start %" G_GINT64_FORMAT " stop %" G_GINT64_FORMAT " time %" G_GINT64_FORMAT " base %" G_GINT64_FORMAT "\n", start, stop, time, base); #endif position = base - start; adjust = position + SCR_MUNGE; if (adjust >= 0) demux->scr_adjust = GSTTIME_TO_MPEGTIME (adjust); else demux->scr_adjust = -GSTTIME_TO_MPEGTIME (-adjust); start = SCR_MUNGE; base = 0; if (stop != -1) stop = start + dur; demux->src_segment.rate = segment->rate; demux->src_segment.applied_rate = segment->applied_rate; demux->src_segment.format = segment->format; demux->src_segment.start = start; demux->src_segment.stop = stop; demux->src_segment.time = time; demux->src_segment.base = base; demux->src_segment.position = position; GST_DEBUG_OBJECT (demux, "sending new segment: rate %g format %d, start: %" G_GINT64_FORMAT ", stop: %" G_GINT64_FORMAT ", time: %" G_GINT64_FORMAT ", base: %" G_GINT64_FORMAT ", scr_adjust: %" G_GINT64_FORMAT "(%" GST_TIME_FORMAT ")", segment->rate, segment->format, start, stop, time, base, demux->scr_adjust, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->scr_adjust))); #if 0 g_print ("sending new segment: rate %g format %d, start: %" G_GINT64_FORMAT ", stop: %" G_GINT64_FORMAT ", time: %" G_GINT64_FORMAT ", base: %" G_GINT64_FORMAT ", scr_adjust: %" G_GINT64_FORMAT "(%" GST_TIME_FORMAT ")\n", segment->rate, segment->format, start, stop, time, base, demux->scr_adjust, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->scr_adjust))); #endif if (demux->in_still && stop != -1) { /* Generate gap buffers, due to closing segment from a still-frame */ gst_flups_demux_send_gap_updates (demux, stop); } gst_event_unref (event); event = gst_event_new_segment (&demux->src_segment); gst_flups_demux_send_event (demux, 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); gboolean in_still; if (gst_video_event_parse_still_frame (event, &in_still)) { /* Remember the still-frame state, so we can generate a pre-roll * GAP event when a segment event arrives */ demux->in_still = in_still; GST_INFO_OBJECT (demux, "still-state now %d", demux->in_still); gst_flups_demux_send_event (demux, event); } else 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; } case GST_EVENT_CAPS: gst_event_unref (event); break; default: gst_flups_demux_send_event (demux, event); break; } return res; } static gboolean gst_flups_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean res = FALSE; GstFluPSDemux *demux = GST_FLUPS_DEMUX (parent); 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; } return res; not_supported: { gst_event_unref (event); return FALSE; } } static gboolean gst_flups_demux_src_query (GstPad * pad, GstObject * parent, GstQuery * query) { gboolean res = FALSE; GstFluPSDemux *demux = GST_FLUPS_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 %d", format); goto not_supported; } pos = demux->base_time; if (demux->current_scr != G_MAXUINT64 && demux->first_scr != G_MAXUINT64) { pos += MPEGTIME_TO_GSTTIME (demux->current_scr - demux->scr_adjust - demux->first_scr); } GST_LOG_OBJECT (demux, "Position at GStreamer Time:%" 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 %d", 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 %d", 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:{ /* Just ask upstream */ res = gst_pad_peer_query (demux->sinkpad, query); break; } default: res = gst_pad_query_default (pad, parent, query); break; } return res; not_supported: 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, 0xaf, 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 } /* 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_flups_demux_parse_pack_start (GstFluPSDemux * demux) { const guint8 *data; guint length; guint32 scr1, scr2; guint64 scr, scr_adjusted, new_rate; guint64 scr_rate_n; guint64 scr_rate_d; GstClockTime new_time; guint avail = gst_adapter_available (demux->adapter); GST_DEBUG ("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_DEBUG ("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_DEBUG_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_DEBUG_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)) { demux->first_scr = scr; demux->first_scr_offset = demux->cur_scr_offset; if (demux->sink_segment.format == GST_FORMAT_TIME) { demux->base_time = demux->sink_segment.time; } else { demux->base_time = MPEGTIME_TO_GSTTIME (demux->first_scr); } /* at begin consider the new_rate as the scr rate, bytes/clock ticks */ scr_rate_n = new_rate; scr_rate_d = CLOCK_FREQ; } 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_DEBUG_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))); 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->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)) { gint64 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_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 (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 (diff > CLOCK_FREQ) { #if 0 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 GST_WARNING_OBJECT (demux, "Unexpected SCR diff of %" G_GINT64_FORMAT, diff); #endif } 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; new_time = MPEGTIME_TO_GSTTIME (scr_adjusted); if (new_time != GST_CLOCK_TIME_NONE) { // g_print ("SCR now %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (new_time)); gst_segment_set_position (&demux->src_segment, GST_FORMAT_TIME, new_time); gst_flups_demux_send_gap_updates (demux, new_time); } /* 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_flups_demux_parse_sys_head (GstFluPSDemux * demux) { guint16 length; const guint8 *data; gboolean csps; 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 */ { 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_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_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; 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); 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); 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_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; 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) { 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 (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 */ nframes = map.data[offset++]; datalen--; GST_DEBUG_OBJECT (demux, "private type 0x%02x, %d frames", id, nframes); } else { GST_DEBUG_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_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 (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 - demux->scr_adjust) > 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_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_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_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 (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_flups_demux_is_pes_sync (guint32 sync) { return ((sync & 0xfc) == 0xbc) || ((sync & 0xe0) == 0xc0) || ((sync & 0xf0) == 0xe0); } /* If we can pull that's prefered */ static gboolean gst_flups_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_flups_demux_sink_activate_push (GstPad * sinkpad, GstObject * parent, gboolean active) { GstFluPSDemux *demux = GST_FLUPS_DEMUX (parent); demux->random_access = FALSE; return TRUE; } #if 0 /* 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_flups_demux_sink_activate_pull (GstPad * sinkpad, GstObject * parent, gboolean active) { GstFluPSDemux *demux = GST_FLUPS_DEMUX (parent); if (active) { GST_DEBUG ("pull mode activated"); demux->random_access = TRUE; return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_flups_demux_loop, sinkpad, NULL); } else { demux->random_access = FALSE; return gst_pad_stop_task (sinkpad); } } #endif static gboolean gst_flups_demux_sink_activate_mode (GstPad * pad, GstObject * parent, GstPadMode mode, gboolean active) { if (mode == GST_PAD_MODE_PUSH) { return gst_flups_demux_sink_activate_push (pad, parent, active); } else if (mode == GST_PAD_MODE_PULL) { // return gst_flups_demux_sink_activate_pull (pad, parent, active); } return FALSE; } static GstFlowReturn gst_flups_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) { GstFluPSDemux *demux = GST_FLUPS_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_flups_demux_mark_discont (demux); /* 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_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: 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->bytes_since_scr = 0; 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->bytes_since_scr = 0; 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); gst_flups_demux_flush (demux); 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; } static void gst_segment_set_position (GstSegment * segment, GstFormat format, guint64 position) { if (segment->format == GST_FORMAT_UNDEFINED) { segment->format = format; } segment->position = position; } gboolean gst_flups_demux_plugin_init (GstPlugin * plugin) { GST_DEBUG_CATEGORY_INIT (mpegpspesfilter_debug, "rsnpesfilter", 0, "MPEG program stream PES filter debug"); GST_DEBUG_CATEGORY_INIT (gstflupsdemux_debug, "rsndvddemux", 0, "MPEG program stream demuxer debug"); return TRUE; }