| /* |
| * mpegtsparse.c - |
| * Copyright (C) 2007 Alessandro Decina |
| * |
| * Authors: |
| * Alessandro Decina <alessandro@nnva.org> |
| * Zaheer Abbas Merali <zaheerabbas at merali dot org> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "mpegtsbase.h" |
| #include "mpegtsparse.h" |
| #include "gstmpegdesc.h" |
| |
| /* latency in mseconds is maximum 100 ms between PCR */ |
| #define TS_LATENCY 100 |
| |
| #define TABLE_ID_UNSET 0xFF |
| #define RUNNING_STATUS_RUNNING 4 |
| |
| GST_DEBUG_CATEGORY_STATIC (mpegts_parse_debug); |
| #define GST_CAT_DEFAULT mpegts_parse_debug |
| |
| typedef struct _MpegTSParsePad MpegTSParsePad; |
| |
| typedef struct |
| { |
| MpegTSBaseProgram program; |
| MpegTSParsePad *tspad; |
| } MpegTSParseProgram; |
| |
| struct _MpegTSParsePad |
| { |
| GstPad *pad; |
| |
| /* the program number that the peer wants on this pad */ |
| gint program_number; |
| MpegTSParseProgram *program; |
| |
| /* set to FALSE before a push and TRUE after */ |
| gboolean pushed; |
| |
| /* the return of the latest push */ |
| GstFlowReturn flow_return; |
| }; |
| |
| static GstStaticPadTemplate src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ") |
| ); |
| |
| static GstStaticPadTemplate program_template = |
| GST_STATIC_PAD_TEMPLATE ("program_%u", GST_PAD_SRC, |
| GST_PAD_REQUEST, |
| GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ") |
| ); |
| |
| enum |
| { |
| PROP_0, |
| PROP_SET_TIMESTAMPS, |
| PROP_SMOOTHING_LATENCY, |
| PROP_PCR_PID, |
| /* FILL ME */ |
| }; |
| |
| static void mpegts_parse_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void mpegts_parse_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static void |
| mpegts_parse_program_started (MpegTSBase * base, MpegTSBaseProgram * program); |
| static void |
| mpegts_parse_program_stopped (MpegTSBase * base, MpegTSBaseProgram * program); |
| |
| static GstFlowReturn |
| mpegts_parse_push (MpegTSBase * base, MpegTSPacketizerPacket * packet, |
| GstMpegtsSection * section); |
| static void mpegts_parse_inspect_packet (MpegTSBase * base, |
| MpegTSPacketizerPacket * packet); |
| |
| static MpegTSParsePad *mpegts_parse_create_tspad (MpegTSParse2 * parse, |
| const gchar * name); |
| static void mpegts_parse_destroy_tspad (MpegTSParse2 * parse, |
| MpegTSParsePad * tspad); |
| |
| static void mpegts_parse_pad_removed (GstElement * element, GstPad * pad); |
| static GstPad *mpegts_parse_request_new_pad (GstElement * element, |
| GstPadTemplate * templ, const gchar * name, const GstCaps * caps); |
| static void mpegts_parse_release_pad (GstElement * element, GstPad * pad); |
| static gboolean mpegts_parse_src_pad_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean push_event (MpegTSBase * base, GstEvent * event); |
| |
| #define mpegts_parse_parent_class parent_class |
| G_DEFINE_TYPE (MpegTSParse2, mpegts_parse, GST_TYPE_MPEGTS_BASE); |
| static void mpegts_parse_reset (MpegTSBase * base); |
| static GstFlowReturn mpegts_parse_input_done (MpegTSBase * base, |
| GstBuffer * buffer); |
| static GstFlowReturn |
| drain_pending_buffers (MpegTSParse2 * parse, gboolean drain_all); |
| |
| static void |
| mpegts_parse_dispose (GObject * object) |
| { |
| MpegTSParse2 *parse = (MpegTSParse2 *) object; |
| |
| gst_flow_combiner_free (parse->flowcombiner); |
| |
| GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); |
| } |
| |
| static void |
| mpegts_parse_class_init (MpegTSParse2Class * klass) |
| { |
| GObjectClass *gobject_class = (GObjectClass *) (klass); |
| GstElementClass *element_class; |
| MpegTSBaseClass *ts_class; |
| |
| gobject_class->set_property = mpegts_parse_set_property; |
| gobject_class->get_property = mpegts_parse_get_property; |
| gobject_class->dispose = mpegts_parse_dispose; |
| |
| g_object_class_install_property (gobject_class, PROP_SET_TIMESTAMPS, |
| g_param_spec_boolean ("set-timestamps", |
| "Timestamp (or re-timestamp) the output stream", |
| "If set, timestamps will be set on the output buffers using " |
| "PCRs and smoothed over the smoothing-latency period", FALSE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_SMOOTHING_LATENCY, |
| g_param_spec_uint ("smoothing-latency", "Smoothing Latency", |
| "Additional latency in microseconds for smoothing jitter in input timestamps on live capture", |
| 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_PCR_PID, |
| g_param_spec_int ("pcr-pid", "PID containing PCR", |
| "Set the PID to use for PCR values (-1 for auto)", |
| -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| element_class = GST_ELEMENT_CLASS (klass); |
| element_class->pad_removed = mpegts_parse_pad_removed; |
| element_class->request_new_pad = mpegts_parse_request_new_pad; |
| element_class->release_pad = mpegts_parse_release_pad; |
| |
| gst_element_class_add_static_pad_template (element_class, &src_template); |
| gst_element_class_add_static_pad_template (element_class, &program_template); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "MPEG transport stream parser", "Codec/Parser", |
| "Parses MPEG2 transport streams", |
| "Alessandro Decina <alessandro@nnva.org>, " |
| "Zaheer Abbas Merali <zaheerabbas at merali dot org>"); |
| |
| ts_class = GST_MPEGTS_BASE_CLASS (klass); |
| ts_class->push = GST_DEBUG_FUNCPTR (mpegts_parse_push); |
| ts_class->push_event = GST_DEBUG_FUNCPTR (push_event); |
| ts_class->program_started = GST_DEBUG_FUNCPTR (mpegts_parse_program_started); |
| ts_class->program_stopped = GST_DEBUG_FUNCPTR (mpegts_parse_program_stopped); |
| ts_class->reset = GST_DEBUG_FUNCPTR (mpegts_parse_reset); |
| ts_class->input_done = GST_DEBUG_FUNCPTR (mpegts_parse_input_done); |
| ts_class->inspect_packet = GST_DEBUG_FUNCPTR (mpegts_parse_inspect_packet); |
| } |
| |
| static void |
| mpegts_parse_init (MpegTSParse2 * parse) |
| { |
| MpegTSBase *base = (MpegTSBase *) parse; |
| |
| base->program_size = sizeof (MpegTSParseProgram); |
| /* We will only need to handle data/section if we have request pads */ |
| base->push_data = FALSE; |
| base->push_section = FALSE; |
| |
| parse->user_pcr_pid = parse->pcr_pid = -1; |
| |
| parse->flowcombiner = gst_flow_combiner_new (); |
| |
| parse->srcpad = gst_pad_new_from_static_template (&src_template, "src"); |
| gst_flow_combiner_add_pad (parse->flowcombiner, parse->srcpad); |
| parse->first = TRUE; |
| gst_pad_set_query_function (parse->srcpad, |
| GST_DEBUG_FUNCPTR (mpegts_parse_src_pad_query)); |
| gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad); |
| |
| parse->have_group_id = FALSE; |
| parse->group_id = G_MAXUINT; |
| } |
| |
| static void |
| mpegts_parse_reset (MpegTSBase * base) |
| { |
| MpegTSParse2 *parse = (MpegTSParse2 *) base; |
| |
| /* Set the various know PIDs we are interested in */ |
| |
| /* CAT */ |
| MPEGTS_BIT_SET (base->known_psi, 1); |
| /* NIT, ST */ |
| MPEGTS_BIT_SET (base->known_psi, 0x10); |
| /* SDT, BAT, ST */ |
| MPEGTS_BIT_SET (base->known_psi, 0x11); |
| /* EIT, ST, CIT (TS 102 323) */ |
| MPEGTS_BIT_SET (base->known_psi, 0x12); |
| /* RST, ST */ |
| MPEGTS_BIT_SET (base->known_psi, 0x13); |
| /* RNT (TS 102 323) */ |
| MPEGTS_BIT_SET (base->known_psi, 0x16); |
| /* inband signalling */ |
| MPEGTS_BIT_SET (base->known_psi, 0x1c); |
| /* measurement */ |
| MPEGTS_BIT_SET (base->known_psi, 0x1d); |
| /* DIT */ |
| MPEGTS_BIT_SET (base->known_psi, 0x1e); |
| /* SIT */ |
| MPEGTS_BIT_SET (base->known_psi, 0x1f); |
| |
| parse->first = TRUE; |
| parse->have_group_id = FALSE; |
| parse->group_id = G_MAXUINT; |
| |
| g_list_free_full (parse->pending_buffers, (GDestroyNotify) gst_buffer_unref); |
| parse->pending_buffers = NULL; |
| |
| parse->current_pcr = GST_CLOCK_TIME_NONE; |
| parse->previous_pcr = GST_CLOCK_TIME_NONE; |
| parse->base_pcr = GST_CLOCK_TIME_NONE; |
| parse->bytes_since_pcr = 0; |
| parse->pcr_pid = parse->user_pcr_pid; |
| parse->ts_offset = 0; |
| } |
| |
| static void |
| mpegts_parse_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| MpegTSParse2 *parse = (MpegTSParse2 *) object; |
| |
| switch (prop_id) { |
| case PROP_SET_TIMESTAMPS: |
| parse->set_timestamps = g_value_get_boolean (value); |
| break; |
| case PROP_SMOOTHING_LATENCY: |
| parse->smoothing_latency = GST_USECOND * g_value_get_uint (value); |
| mpegts_packetizer_set_pcr_discont_threshold (GST_MPEGTS_BASE |
| (parse)->packetizer, parse->smoothing_latency); |
| break; |
| case PROP_PCR_PID: |
| parse->pcr_pid = parse->user_pcr_pid = g_value_get_int (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| } |
| } |
| |
| static void |
| mpegts_parse_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| MpegTSParse2 *parse = (MpegTSParse2 *) object; |
| |
| switch (prop_id) { |
| case PROP_SET_TIMESTAMPS: |
| g_value_set_boolean (value, parse->set_timestamps); |
| break; |
| case PROP_SMOOTHING_LATENCY: |
| g_value_set_uint (value, parse->smoothing_latency / GST_USECOND); |
| break; |
| case PROP_PCR_PID: |
| g_value_set_int (value, parse->pcr_pid); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| } |
| } |
| |
| static gboolean |
| prepare_src_pad (MpegTSBase * base, MpegTSParse2 * parse) |
| { |
| GstEvent *event; |
| gchar *stream_id; |
| GstCaps *caps; |
| |
| if (!parse->first) |
| return TRUE; |
| |
| /* If there's no packet_size yet, we can't set caps yet */ |
| if (G_UNLIKELY (base->packetizer->packet_size == 0)) |
| return FALSE; |
| |
| stream_id = |
| gst_pad_create_stream_id (parse->srcpad, GST_ELEMENT_CAST (base), |
| "multi-program"); |
| |
| event = |
| gst_pad_get_sticky_event (parse->parent.sinkpad, GST_EVENT_STREAM_START, |
| 0); |
| if (event) { |
| if (gst_event_parse_group_id (event, &parse->group_id)) |
| parse->have_group_id = TRUE; |
| else |
| parse->have_group_id = FALSE; |
| gst_event_unref (event); |
| } else if (!parse->have_group_id) { |
| parse->have_group_id = TRUE; |
| parse->group_id = gst_util_group_id_next (); |
| } |
| event = gst_event_new_stream_start (stream_id); |
| if (parse->have_group_id) |
| gst_event_set_group_id (event, parse->group_id); |
| |
| gst_pad_push_event (parse->srcpad, event); |
| g_free (stream_id); |
| |
| caps = gst_caps_new_simple ("video/mpegts", |
| "systemstream", G_TYPE_BOOLEAN, TRUE, |
| "packetsize", G_TYPE_INT, base->packetizer->packet_size, NULL); |
| |
| gst_pad_set_caps (parse->srcpad, caps); |
| gst_caps_unref (caps); |
| |
| /* If setting output timestamps, ensure that the output segment is TIME */ |
| if (parse->set_timestamps == FALSE || base->segment.format == GST_FORMAT_TIME) |
| gst_pad_push_event (parse->srcpad, gst_event_new_segment (&base->segment)); |
| else { |
| GstSegment seg; |
| gst_segment_init (&seg, GST_FORMAT_TIME); |
| GST_DEBUG_OBJECT (parse, |
| "Generating time output segment %" GST_SEGMENT_FORMAT, &seg); |
| gst_pad_push_event (parse->srcpad, gst_event_new_segment (&seg)); |
| } |
| |
| parse->first = FALSE; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| push_event (MpegTSBase * base, GstEvent * event) |
| { |
| MpegTSParse2 *parse = (MpegTSParse2 *) base; |
| GList *tmp; |
| |
| if (G_UNLIKELY (parse->first)) { |
| /* We will send the segment when really starting */ |
| if (G_UNLIKELY (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT)) { |
| gst_event_unref (event); |
| return TRUE; |
| } |
| prepare_src_pad (base, parse); |
| } |
| if (G_UNLIKELY (GST_EVENT_TYPE (event) == GST_EVENT_EOS)) |
| drain_pending_buffers (parse, TRUE); |
| |
| if (G_UNLIKELY (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT)) |
| parse->ts_offset = 0; |
| |
| for (tmp = parse->srcpads; tmp; tmp = tmp->next) { |
| GstPad *pad = (GstPad *) tmp->data; |
| if (pad) { |
| gst_event_ref (event); |
| gst_pad_push_event (pad, event); |
| } |
| } |
| |
| gst_pad_push_event (parse->srcpad, event); |
| |
| return TRUE; |
| } |
| |
| static MpegTSParsePad * |
| mpegts_parse_create_tspad (MpegTSParse2 * parse, const gchar * pad_name) |
| { |
| GstPad *pad; |
| MpegTSParsePad *tspad; |
| |
| pad = gst_pad_new_from_static_template (&program_template, pad_name); |
| gst_pad_set_query_function (pad, |
| GST_DEBUG_FUNCPTR (mpegts_parse_src_pad_query)); |
| |
| /* create our wrapper */ |
| tspad = g_new0 (MpegTSParsePad, 1); |
| tspad->pad = pad; |
| tspad->program_number = -1; |
| tspad->program = NULL; |
| tspad->pushed = FALSE; |
| tspad->flow_return = GST_FLOW_NOT_LINKED; |
| gst_pad_set_element_private (pad, tspad); |
| gst_flow_combiner_add_pad (parse->flowcombiner, pad); |
| |
| return tspad; |
| } |
| |
| static void |
| mpegts_parse_destroy_tspad (MpegTSParse2 * parse, MpegTSParsePad * tspad) |
| { |
| /* free the wrapper */ |
| g_free (tspad); |
| } |
| |
| static void |
| mpegts_parse_pad_removed (GstElement * element, GstPad * pad) |
| { |
| MpegTSParsePad *tspad; |
| MpegTSBase *base = (MpegTSBase *) element; |
| MpegTSParse2 *parse = GST_MPEGTS_PARSE (element); |
| |
| if (gst_pad_get_direction (pad) == GST_PAD_SINK) |
| return; |
| |
| tspad = (MpegTSParsePad *) gst_pad_get_element_private (pad); |
| if (tspad) { |
| mpegts_parse_destroy_tspad (parse, tspad); |
| |
| parse->srcpads = g_list_remove_all (parse->srcpads, pad); |
| } |
| if (parse->srcpads == NULL) { |
| base->push_data = FALSE; |
| base->push_section = FALSE; |
| } |
| |
| if (GST_ELEMENT_CLASS (parent_class)->pad_removed) |
| GST_ELEMENT_CLASS (parent_class)->pad_removed (element, pad); |
| } |
| |
| static GstPad * |
| mpegts_parse_request_new_pad (GstElement * element, GstPadTemplate * template, |
| const gchar * padname, const GstCaps * caps) |
| { |
| MpegTSBase *base = (MpegTSBase *) element; |
| MpegTSParse2 *parse; |
| MpegTSParsePad *tspad; |
| MpegTSParseProgram *parseprogram; |
| GstPad *pad; |
| gint program_num = -1; |
| GstEvent *event; |
| gchar *stream_id; |
| |
| g_return_val_if_fail (template != NULL, NULL); |
| g_return_val_if_fail (GST_IS_MPEGTS_PARSE (element), NULL); |
| g_return_val_if_fail (padname != NULL, NULL); |
| |
| sscanf (padname + 8, "%d", &program_num); |
| |
| GST_DEBUG_OBJECT (element, "padname:%s, program:%d", padname, program_num); |
| |
| parse = GST_MPEGTS_PARSE (element); |
| |
| tspad = mpegts_parse_create_tspad (parse, padname); |
| tspad->program_number = program_num; |
| |
| /* Find if the program is already active */ |
| parseprogram = |
| (MpegTSParseProgram *) mpegts_base_get_program (GST_MPEGTS_BASE (parse), |
| program_num); |
| if (parseprogram) { |
| tspad->program = parseprogram; |
| parseprogram->tspad = tspad; |
| } |
| |
| pad = tspad->pad; |
| parse->srcpads = g_list_append (parse->srcpads, pad); |
| base->push_data = TRUE; |
| base->push_section = TRUE; |
| |
| gst_pad_set_active (pad, TRUE); |
| |
| stream_id = gst_pad_create_stream_id (pad, element, padname + 8); |
| |
| event = |
| gst_pad_get_sticky_event (parse->parent.sinkpad, GST_EVENT_STREAM_START, |
| 0); |
| if (event) { |
| if (gst_event_parse_group_id (event, &parse->group_id)) |
| parse->have_group_id = TRUE; |
| else |
| parse->have_group_id = FALSE; |
| gst_event_unref (event); |
| } else if (!parse->have_group_id) { |
| parse->have_group_id = TRUE; |
| parse->group_id = gst_util_group_id_next (); |
| } |
| event = gst_event_new_stream_start (stream_id); |
| if (parse->have_group_id) |
| gst_event_set_group_id (event, parse->group_id); |
| |
| gst_pad_push_event (pad, event); |
| g_free (stream_id); |
| |
| gst_element_add_pad (element, pad); |
| |
| return pad; |
| } |
| |
| static void |
| mpegts_parse_release_pad (GstElement * element, GstPad * pad) |
| { |
| MpegTSParse2 *parse = (MpegTSParse2 *) element; |
| |
| gst_pad_set_active (pad, FALSE); |
| /* we do the cleanup in GstElement::pad-removed */ |
| gst_flow_combiner_remove_pad (parse->flowcombiner, pad); |
| gst_element_remove_pad (element, pad); |
| } |
| |
| static GstFlowReturn |
| mpegts_parse_tspad_push_section (MpegTSParse2 * parse, MpegTSParsePad * tspad, |
| GstMpegtsSection * section, MpegTSPacketizerPacket * packet) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| gboolean to_push = TRUE; |
| |
| if (tspad->program_number != -1) { |
| if (tspad->program) { |
| /* we push all sections to all pads except PMTs which we |
| * only push to pads meant to receive that program number */ |
| if (section->table_id == 0x02) { |
| /* PMT */ |
| if (section->subtable_extension != tspad->program_number) |
| to_push = FALSE; |
| } |
| } else if (section->table_id != 0x00) { |
| /* there's a program filter on the pad but the PMT for the program has not |
| * been parsed yet, ignore the pad until we get a PMT. |
| * But we always allow PAT to go through */ |
| to_push = FALSE; |
| } |
| } |
| |
| GST_DEBUG_OBJECT (parse, |
| "pushing section: %d program number: %d table_id: %d", to_push, |
| tspad->program_number, section->table_id); |
| |
| if (to_push) { |
| GstBuffer *buf = |
| gst_buffer_new_and_alloc (packet->data_end - packet->data_start); |
| gst_buffer_fill (buf, 0, packet->data_start, |
| packet->data_end - packet->data_start); |
| ret = gst_pad_push (tspad->pad, buf); |
| ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret); |
| } |
| |
| GST_LOG_OBJECT (parse, "Returning %s", gst_flow_get_name (ret)); |
| return ret; |
| } |
| |
| static GstFlowReturn |
| mpegts_parse_tspad_push (MpegTSParse2 * parse, MpegTSParsePad * tspad, |
| MpegTSPacketizerPacket * packet) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| MpegTSBaseProgram *bp = NULL; |
| |
| if (tspad->program_number != -1) { |
| if (tspad->program) |
| bp = (MpegTSBaseProgram *) tspad->program; |
| else |
| bp = mpegts_base_get_program ((MpegTSBase *) parse, |
| tspad->program_number); |
| } |
| |
| if (bp) { |
| if (packet->pid == bp->pmt_pid || bp->streams == NULL |
| || bp->streams[packet->pid]) { |
| GstBuffer *buf = |
| gst_buffer_new_and_alloc (packet->data_end - packet->data_start); |
| gst_buffer_fill (buf, 0, packet->data_start, |
| packet->data_end - packet->data_start); |
| /* push if there's no filter or if the pid is in the filter */ |
| ret = gst_pad_push (tspad->pad, buf); |
| ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret); |
| } |
| } |
| GST_DEBUG_OBJECT (parse, "Returning %s", gst_flow_get_name (ret)); |
| |
| return ret; |
| } |
| |
| static void |
| pad_clear_for_push (GstPad * pad, MpegTSParse2 * parse) |
| { |
| MpegTSParsePad *tspad = (MpegTSParsePad *) gst_pad_get_element_private (pad); |
| |
| tspad->flow_return = GST_FLOW_NOT_LINKED; |
| tspad->pushed = FALSE; |
| } |
| |
| static GstFlowReturn |
| mpegts_parse_push (MpegTSBase * base, MpegTSPacketizerPacket * packet, |
| GstMpegtsSection * section) |
| { |
| MpegTSParse2 *parse = (MpegTSParse2 *) base; |
| guint32 pads_cookie; |
| gboolean done = FALSE; |
| GstPad *pad = NULL; |
| MpegTSParsePad *tspad; |
| GstFlowReturn ret; |
| GList *srcpads; |
| |
| GST_OBJECT_LOCK (parse); |
| srcpads = parse->srcpads; |
| |
| /* clear tspad->pushed on pads */ |
| g_list_foreach (srcpads, (GFunc) pad_clear_for_push, parse); |
| if (srcpads) |
| ret = GST_FLOW_NOT_LINKED; |
| else |
| ret = GST_FLOW_OK; |
| |
| /* Get cookie and source pads list */ |
| pads_cookie = GST_ELEMENT_CAST (parse)->pads_cookie; |
| if (G_LIKELY (srcpads)) { |
| pad = GST_PAD_CAST (srcpads->data); |
| g_object_ref (pad); |
| } |
| GST_OBJECT_UNLOCK (parse); |
| |
| while (pad && !done) { |
| tspad = gst_pad_get_element_private (pad); |
| |
| if (G_LIKELY (!tspad->pushed)) { |
| if (section) { |
| tspad->flow_return = |
| mpegts_parse_tspad_push_section (parse, tspad, section, packet); |
| } else { |
| tspad->flow_return = mpegts_parse_tspad_push (parse, tspad, packet); |
| } |
| tspad->pushed = TRUE; |
| |
| if (G_UNLIKELY (tspad->flow_return != GST_FLOW_OK |
| && tspad->flow_return != GST_FLOW_NOT_LINKED)) { |
| /* return the error upstream */ |
| ret = tspad->flow_return; |
| done = TRUE; |
| } |
| |
| } |
| |
| if (ret == GST_FLOW_NOT_LINKED) |
| ret = tspad->flow_return; |
| |
| g_object_unref (pad); |
| |
| if (G_UNLIKELY (!done)) { |
| GST_OBJECT_LOCK (parse); |
| if (G_UNLIKELY (pads_cookie != GST_ELEMENT_CAST (parse)->pads_cookie)) { |
| /* resync */ |
| GST_DEBUG ("resync"); |
| pads_cookie = GST_ELEMENT_CAST (parse)->pads_cookie; |
| srcpads = parse->srcpads; |
| } else { |
| GST_DEBUG ("getting next pad"); |
| /* Get next pad */ |
| srcpads = g_list_next (srcpads); |
| } |
| |
| if (srcpads) { |
| pad = GST_PAD_CAST (srcpads->data); |
| g_object_ref (pad); |
| } else |
| done = TRUE; |
| GST_OBJECT_UNLOCK (parse); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static void |
| mpegts_parse_inspect_packet (MpegTSBase * base, MpegTSPacketizerPacket * packet) |
| { |
| MpegTSParse2 *parse = GST_MPEGTS_PARSE (base); |
| GST_LOG ("pid 0x%04x pusi:%d, afc:%d, cont:%d, payload:%p PCR %" |
| G_GUINT64_FORMAT, packet->pid, packet->payload_unit_start_indicator, |
| packet->scram_afc_cc & 0x30, |
| FLAGS_CONTINUITY_COUNTER (packet->scram_afc_cc), packet->payload, |
| packet->pcr); |
| |
| /* Store the PCR if desired */ |
| if (parse->current_pcr == GST_CLOCK_TIME_NONE && |
| packet->afc_flags & MPEGTS_AFC_PCR_FLAG) { |
| /* Take this as the pcr_pid if set to auto-select */ |
| if (parse->pcr_pid == -1) |
| parse->pcr_pid = packet->pid; |
| /* Check the PCR-PID matches the program we want for multiple programs */ |
| if (parse->pcr_pid == packet->pid) { |
| parse->current_pcr = PCRTIME_TO_GSTTIME (packet->pcr); |
| if (parse->base_pcr == GST_CLOCK_TIME_NONE) { |
| parse->base_pcr = parse->current_pcr; |
| } |
| } |
| } |
| } |
| |
| static GstClockTime |
| get_pending_timestamp_diff (MpegTSParse2 * parse) |
| { |
| GList *l; |
| GstClockTime first_ts, last_ts; |
| |
| if (parse->pending_buffers == NULL) |
| return GST_CLOCK_TIME_NONE; |
| |
| l = g_list_last (parse->pending_buffers); |
| first_ts = GST_BUFFER_PTS (l->data); |
| if (first_ts == GST_CLOCK_TIME_NONE) |
| return GST_CLOCK_TIME_NONE; |
| |
| l = g_list_first (parse->pending_buffers); |
| last_ts = GST_BUFFER_PTS (l->data); |
| if (last_ts == GST_CLOCK_TIME_NONE) |
| return GST_CLOCK_TIME_NONE; |
| |
| return last_ts - first_ts; |
| } |
| |
| static GstFlowReturn |
| drain_pending_buffers (MpegTSParse2 * parse, gboolean drain_all) |
| { |
| MpegTSBase *base = (MpegTSBase *) (parse); |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstClockTime start_ts; |
| GstClockTime pcr = GST_CLOCK_TIME_NONE; |
| GstClockTime pcr_diff = 0; |
| gsize pcr_bytes, bytes_since_pcr, pos; |
| GstBuffer *buffer; |
| GList *l, *end = NULL; |
| |
| if (parse->pending_buffers == NULL) |
| return GST_FLOW_OK; /* Nothing to push */ |
| |
| /* |
| * There are 4 cases: |
| * 1 We get a buffer with no PCR -> it's the head of the list |
| * -> Do nothing, unless it's EOS |
| * 2 We get a buffer with a PCR, it's the first PCR we've seen, and belongs |
| * to the buffer at the head of the list |
| * -> Push any buffers in the list except the head, |
| * using a smoothing of their timestamps to land at the PCR |
| * -> store new PCR as the previous PCR, bytes_since_pcr = sizeof (buffer); |
| * 3 It's EOS (drain_all == TRUE, current_pcr == NONE) |
| * -> Push any buffers in the list using a smoothing of their timestamps |
| * starting at the previous PCR or first TS |
| * 4 We get a buffer with a PCR, and have a previous PCR |
| * -> If distance > smoothing_latency, |
| * output buffers except the last in the pending queue using |
| * piecewise-linear timestamps |
| * -> store new PCR as the previous PCR, bytes_since_pcr = sizeof (buffer); |
| */ |
| |
| /* Case 1 */ |
| if (!GST_CLOCK_TIME_IS_VALID (parse->current_pcr) && !drain_all) |
| return GST_FLOW_OK; |
| |
| if (GST_CLOCK_TIME_IS_VALID (parse->current_pcr)) { |
| pcr = mpegts_packetizer_pts_to_ts (base->packetizer, |
| parse->current_pcr, parse->pcr_pid); |
| parse->current_pcr = GST_CLOCK_TIME_NONE; |
| } |
| |
| /* The bytes of the last buffer are after the PCR */ |
| buffer = GST_BUFFER (g_list_nth_data (parse->pending_buffers, 0)); |
| bytes_since_pcr = gst_buffer_get_size (buffer); |
| |
| pcr_bytes = parse->bytes_since_pcr - bytes_since_pcr; |
| |
| if (!drain_all) |
| end = g_list_first (parse->pending_buffers); |
| |
| /* Case 2 */ |
| if (!GST_CLOCK_TIME_IS_VALID (parse->previous_pcr)) { |
| pcr_diff = get_pending_timestamp_diff (parse); |
| |
| /* Calculate the start_ts that ends at the end timestamp */ |
| start_ts = GST_CLOCK_TIME_NONE; |
| if (end) { |
| start_ts = GST_BUFFER_PTS (GST_BUFFER (end->data)); |
| if (start_ts > pcr_diff) |
| start_ts -= pcr_diff; |
| } |
| } else if (drain_all) { /* Case 3 */ |
| start_ts = parse->previous_pcr; |
| pcr_diff = get_pending_timestamp_diff (parse); |
| } else { /* Case 4 */ |
| start_ts = parse->previous_pcr; |
| if (GST_CLOCK_TIME_IS_VALID (pcr) && pcr > start_ts) |
| pcr_diff = GST_CLOCK_DIFF (start_ts, pcr); |
| |
| /* Make sure PCR observations are sufficiently far apart */ |
| if (drain_all == FALSE && pcr_diff < parse->smoothing_latency) |
| return GST_FLOW_OK; |
| } |
| |
| GST_INFO_OBJECT (parse, "Pushing buffers - startTS %" GST_TIME_FORMAT |
| " duration %" GST_TIME_FORMAT " %" G_GSIZE_FORMAT " bytes", |
| GST_TIME_ARGS (start_ts), GST_TIME_ARGS (pcr_diff), pcr_bytes); |
| |
| /* Now, push buffers out pacing timestamps over pcr_diff time and pcr_bytes */ |
| pos = 0; |
| l = g_list_last (parse->pending_buffers); |
| while (l != end) { |
| GList *p; |
| GstClockTime out_ts = start_ts; |
| |
| buffer = gst_buffer_make_writable (GST_BUFFER (l->data)); |
| |
| if (out_ts != GST_CLOCK_TIME_NONE && pcr_diff != GST_CLOCK_TIME_NONE && |
| pcr_bytes && pos) |
| out_ts += gst_util_uint64_scale (pcr_diff, pos, pcr_bytes); |
| |
| pos += gst_buffer_get_size (buffer); |
| |
| GST_DEBUG_OBJECT (parse, |
| "InputTS %" GST_TIME_FORMAT " out %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (GST_BUFFER_PTS (buffer)), GST_TIME_ARGS (out_ts)); |
| |
| GST_BUFFER_PTS (buffer) = out_ts + parse->ts_offset; |
| GST_BUFFER_DTS (buffer) = out_ts + parse->ts_offset; |
| if (ret == GST_FLOW_OK) { |
| ret = gst_pad_push (parse->srcpad, buffer); |
| ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret); |
| } else |
| gst_buffer_unref (buffer); |
| |
| /* Free this list node and move to the next */ |
| p = g_list_previous (l); |
| parse->pending_buffers = g_list_delete_link (parse->pending_buffers, l); |
| l = p; |
| } |
| |
| parse->pending_buffers = end; |
| parse->bytes_since_pcr = bytes_since_pcr; |
| parse->previous_pcr = pcr; |
| return ret; |
| } |
| |
| static GstFlowReturn |
| mpegts_parse_input_done (MpegTSBase * base, GstBuffer * buffer) |
| { |
| MpegTSParse2 *parse = GST_MPEGTS_PARSE (base); |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| GST_LOG_OBJECT (parse, "Received buffer %" GST_PTR_FORMAT, buffer); |
| |
| if (parse->current_pcr != GST_CLOCK_TIME_NONE) { |
| GST_DEBUG_OBJECT (parse, |
| "InputTS %" GST_TIME_FORMAT " PCR %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (GST_BUFFER_PTS (buffer)), |
| GST_TIME_ARGS (mpegts_packetizer_pts_to_ts (base->packetizer, |
| parse->current_pcr, parse->pcr_pid))); |
| } |
| |
| if (parse->set_timestamps || parse->first) { |
| parse->pending_buffers = g_list_prepend (parse->pending_buffers, buffer); |
| parse->bytes_since_pcr += gst_buffer_get_size (buffer); |
| buffer = NULL; |
| } |
| |
| if (!prepare_src_pad (base, parse)) |
| return GST_FLOW_OK; |
| |
| if (parse->pending_buffers != NULL) { |
| /* Don't keep pending_buffers if not setting output timestamps */ |
| gboolean drain_all = (parse->set_timestamps == FALSE); |
| ret = drain_pending_buffers (parse, drain_all); |
| if (ret != GST_FLOW_OK) { |
| if (buffer) |
| gst_buffer_unref (buffer); |
| return ret; |
| } |
| } |
| |
| if (buffer != NULL) { |
| ret = gst_pad_push (parse->srcpad, buffer); |
| ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret); |
| } |
| |
| return ret; |
| } |
| |
| static MpegTSParsePad * |
| find_pad_for_program (MpegTSParse2 * parse, guint program_number) |
| { |
| GList *tmp; |
| |
| for (tmp = parse->srcpads; tmp; tmp = tmp->next) { |
| MpegTSParsePad *tspad = gst_pad_get_element_private ((GstPad *) tmp->data); |
| |
| if (tspad->program_number == program_number) |
| return tspad; |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| mpegts_parse_program_started (MpegTSBase * base, MpegTSBaseProgram * program) |
| { |
| MpegTSParse2 *parse = GST_MPEGTS_PARSE (base); |
| MpegTSParseProgram *parseprogram = (MpegTSParseProgram *) program; |
| MpegTSParsePad *tspad; |
| |
| /* If we have a request pad for that program, activate it */ |
| tspad = find_pad_for_program (parse, program->program_number); |
| |
| if (tspad) { |
| tspad->program = parseprogram; |
| parseprogram->tspad = tspad; |
| } |
| } |
| |
| static void |
| mpegts_parse_program_stopped (MpegTSBase * base, MpegTSBaseProgram * program) |
| { |
| MpegTSParse2 *parse = GST_MPEGTS_PARSE (base); |
| MpegTSParseProgram *parseprogram = (MpegTSParseProgram *) program; |
| MpegTSParsePad *tspad; |
| |
| /* If we have a request pad for that program, activate it */ |
| tspad = find_pad_for_program (parse, program->program_number); |
| |
| if (tspad) { |
| tspad->program = NULL; |
| parseprogram->tspad = NULL; |
| } |
| |
| parse->pcr_pid = -1; |
| parse->ts_offset += parse->current_pcr - parse->base_pcr; |
| parse->base_pcr = GST_CLOCK_TIME_NONE; |
| } |
| |
| static gboolean |
| mpegts_parse_src_pad_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| MpegTSParse2 *parse = GST_MPEGTS_PARSE (parent); |
| gboolean res; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_LATENCY: |
| { |
| if ((res = gst_pad_peer_query (((MpegTSBase *) parse)->sinkpad, query))) { |
| gboolean is_live; |
| GstClockTime min_latency, max_latency; |
| |
| gst_query_parse_latency (query, &is_live, &min_latency, &max_latency); |
| if (is_live) { |
| GstClockTime extra_latency = TS_LATENCY * GST_MSECOND; |
| if (parse->set_timestamps) { |
| extra_latency = MAX (extra_latency, parse->smoothing_latency); |
| } |
| min_latency += extra_latency; |
| if (max_latency != GST_CLOCK_TIME_NONE) |
| max_latency += extra_latency; |
| } |
| |
| gst_query_set_latency (query, is_live, min_latency, max_latency); |
| } |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, parent, query); |
| } |
| return res; |
| } |
| |
| gboolean |
| gst_mpegtsparse_plugin_init (GstPlugin * plugin) |
| { |
| GST_DEBUG_CATEGORY_INIT (mpegts_parse_debug, "tsparse", 0, |
| "MPEG transport stream parser"); |
| |
| return gst_element_register (plugin, "tsparse", |
| GST_RANK_NONE, GST_TYPE_MPEGTS_PARSE); |
| } |