/* * mpegtsparse.c - * Copyright (C) 2007 Alessandro Decina * * Authors: * Alessandro Decina * Zaheer Abbas Merali * * 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 #include #include #include "mpegtsbase.h" #include "mpegtsparse.h" #include "gstmpegdesc.h" /* latency in mseconds */ #define TS_LATENCY 700 #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 { ARG_0, /* FILL ME */ }; 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, MpegTSPacketizerSection * section); 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 void mpegts_parse_class_init (MpegTSParse2Class * klass) { GstElementClass *element_class; MpegTSBaseClass *ts_class; 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_pad_template (element_class, gst_static_pad_template_get (&src_template)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&program_template)); gst_element_class_set_static_metadata (element_class, "MPEG transport stream parser", "Codec/Parser", "Parses MPEG2 transport streams", "Alessandro Decina , " "Zaheer Abbas Merali "); 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); } static void mpegts_parse_init (MpegTSParse2 * parse) { GST_MPEGTS_BASE (parse)->program_size = sizeof (MpegTSParseProgram); parse->srcpad = gst_pad_new_from_static_template (&src_template, "src"); parse->first = TRUE; gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad); } static void mpegts_parse_reset (MpegTSBase * 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); GST_MPEGTS_PARSE (base)->first = TRUE; } static void prepare_src_pad (MpegTSBase * base, MpegTSParse2 * parse) { if (base->packetizer->know_packet_size) { gchar *stream_id; GstCaps *caps; stream_id = gst_pad_create_stream_id (parse->srcpad, GST_ELEMENT_CAST (base), "multi-program"); gst_pad_push_event (parse->srcpad, gst_event_new_stream_start (stream_id)); 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); gst_pad_push_event (parse->srcpad, gst_event_new_segment (&base->segment)); parse->first = FALSE; } } 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); } 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); 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; 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 (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) { MpegTSParse2 *parse; MpegTSParsePad *tspad; MpegTSParseProgram *parseprogram; GstPad *pad; gint program_num = -1; 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); gst_pad_set_active (pad, TRUE); stream_id = gst_pad_create_stream_id (pad, element, padname + 8); gst_pad_push_event (pad, gst_event_new_stream_start (stream_id)); g_free (stream_id); gst_element_add_pad (element, pad); return pad; } static void mpegts_parse_release_pad (GstElement * element, GstPad * pad) { gst_pad_set_active (pad, FALSE); /* we do the cleanup in GstElement::pad-removed */ gst_element_remove_pad (element, pad); } static GstFlowReturn mpegts_parse_tspad_push_section (MpegTSParse2 * parse, MpegTSParsePad * tspad, MpegTSPacketizerSection * 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 { /* 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 */ 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); } return ret; } static GstFlowReturn mpegts_parse_tspad_push (MpegTSParse2 * parse, MpegTSParsePad * tspad, MpegTSPacketizerPacket * packet) { GstFlowReturn ret = GST_FLOW_OK; MpegTSBaseStream **pad_pids = NULL; if (tspad->program_number != -1) { if (tspad->program) { MpegTSBaseProgram *bp = (MpegTSBaseProgram *) tspad->program; pad_pids = bp->streams; } else { /* 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 */ goto out; } } if (pad_pids == NULL || pad_pids[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); } out: 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, MpegTSPacketizerSection * section) { MpegTSParse2 *parse = (MpegTSParse2 *) base; guint32 pads_cookie; gboolean done = FALSE; GstPad *pad = NULL; MpegTSParsePad *tspad; GstFlowReturn ret; GList *srcpads; /* Shortcut: If no request pads exist, just return */ if (parse->srcpads == NULL) return GST_FLOW_OK; 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 GstFlowReturn mpegts_parse_input_done (MpegTSBase * base, GstBuffer * buffer) { MpegTSParse2 *parse = GST_MPEGTS_PARSE (base); if (G_UNLIKELY (parse->first)) prepare_src_pad (base, parse); return gst_pad_push (parse->srcpad, buffer); } 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; } } 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) { min_latency += TS_LATENCY * GST_MSECOND; if (max_latency != GST_CLOCK_TIME_NONE) max_latency += TS_LATENCY * GST_MSECOND; } 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"); gst_mpegtsdesc_init_debug (); return gst_element_register (plugin, "tsparse", GST_RANK_NONE, GST_TYPE_MPEGTS_PARSE); }