From f6aeac78682cf50372612b368747eabc008d13a8 Mon Sep 17 00:00:00 2001 From: Zaheer Abbas Merali Date: Wed, 8 Oct 2008 15:25:24 +0000 Subject: Move of mpegtsparse to mpegdemux. Original commit message from CVS: * configure.ac: * gst-plugins-bad.spec.in: * gst/mpegdemux/Makefile.am: * gst/mpegdemux/flumpegdemux.c: * gst/mpegdemux/gstmpegdesc.c: * gst/mpegdemux/gstmpegdesc.h: * gst/mpegdemux/mpegtspacketizer.c: * gst/mpegdemux/mpegtspacketizer.h: * gst/mpegdemux/mpegtsparse.c: * gst/mpegdemux/mpegtsparse.h: Move of mpegtsparse to mpegdemux. Fixes #555193. --- ChangeLog | 15 + configure.ac | 2 - gst-plugins-bad.spec.in | 1 - gst/mpegdemux/Makefile.am | 8 +- gst/mpegdemux/flumpegdemux.c | 5 +- gst/mpegdemux/gstmpegdesc.c | 111 +- gst/mpegdemux/gstmpegdesc.h | 98 +- gst/mpegdemux/mpegtspacketizer.c | 2234 ++++++++++++++++++++++++++++++++++++++ gst/mpegdemux/mpegtspacketizer.h | 135 +++ gst/mpegdemux/mpegtsparse.c | 1293 ++++++++++++++++++++++ gst/mpegdemux/mpegtsparse.h | 83 ++ 11 files changed, 3913 insertions(+), 72 deletions(-) create mode 100644 gst/mpegdemux/mpegtspacketizer.c create mode 100644 gst/mpegdemux/mpegtspacketizer.h create mode 100644 gst/mpegdemux/mpegtsparse.c create mode 100644 gst/mpegdemux/mpegtsparse.h diff --git a/ChangeLog b/ChangeLog index 951b7c66..2d7fa884 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2008-10-08 Zaheer Abbas Merali + + * configure.ac: + * gst-plugins-bad.spec.in: + * gst/mpegdemux/Makefile.am: + * gst/mpegdemux/flumpegdemux.c: + * gst/mpegdemux/gstmpegdesc.c: + * gst/mpegdemux/gstmpegdesc.h: + * gst/mpegdemux/mpegtspacketizer.c: + * gst/mpegdemux/mpegtspacketizer.h: + * gst/mpegdemux/mpegtsparse.c: + * gst/mpegdemux/mpegtsparse.h: + Move of mpegtsparse to mpegdemux. + Fixes #555193. + 2008-10-08 Sebastian Dröge * gst/mpegdemux/gstmpegdemux.c: (gst_flups_demux_send_data), diff --git a/configure.ac b/configure.ac index 08752f03..71847892 100644 --- a/configure.ac +++ b/configure.ac @@ -259,7 +259,6 @@ AG_GST_CHECK_PLUGIN(h264parse) AG_GST_CHECK_PLUGIN(librfb) AG_GST_CHECK_PLUGIN(modplug) AG_GST_CHECK_PLUGIN(mpegdemux) -AG_GST_CHECK_PLUGIN(mpegtsparse) AG_GST_CHECK_PLUGIN(mpegtsmux) AG_GST_CHECK_PLUGIN(mpeg4videoparse) AG_GST_CHECK_PLUGIN(mpegvideoparse) @@ -1413,7 +1412,6 @@ gst/librfb/Makefile gst/modplug/Makefile gst/modplug/libmodplug/Makefile gst/mpegdemux/Makefile -gst/mpegtsparse/Makefile gst/mpegtsmux/Makefile gst/mpegtsmux/tsmux/Makefile gst/mpeg4videoparse/Makefile diff --git a/gst-plugins-bad.spec.in b/gst-plugins-bad.spec.in index 97918ead..218d25ac 100644 --- a/gst-plugins-bad.spec.in +++ b/gst-plugins-bad.spec.in @@ -93,7 +93,6 @@ rm -rf $RPM_BUILD_ROOT %{_libdir}/gstreamer-%{majorminor}/libgstdvdspu.so %{_libdir}/gstreamer-%{majorminor}/libgstfestival.so %{_libdir}/gstreamer-%{majorminor}/libgstflvdemux.so -%{_libdir}/gstreamer-%{majorminor}/libgstmpegtsparse.so %{_libdir}/gstreamer-%{majorminor}/libgststereo.so %{_libdir}/gstreamer-%{majorminor}/libgstvcdsrc.so %{_libdir}/gstreamer-%{majorminor}/libgstdvb.so diff --git a/gst/mpegdemux/Makefile.am b/gst/mpegdemux/Makefile.am index e0784750..215046e6 100644 --- a/gst/mpegdemux/Makefile.am +++ b/gst/mpegdemux/Makefile.am @@ -9,7 +9,9 @@ libgstmpegdemux_la_SOURCES = \ gstmpegdesc.c \ gstmpegtsdemux.c \ gstpesfilter.c \ - gstsectionfilter.c + gstsectionfilter.c \ + mpegtsparse.c \ + mpegtspacketizer.c libgstmpegdemux_la_CFLAGS = $(GST_CFLAGS) $(LIBOIL_CFLAGS) libgstmpegdemux_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(LIBOIL_LIBS) @@ -24,5 +26,7 @@ noinst_HEADERS = \ flutspatinfo.h \ flutspmtinfo.h \ flutspmtstreaminfo.h \ - gstsectionfilter.h + gstsectionfilter.h \ + mpegtspacketizer.h \ + mpegtsparse.h diff --git a/gst/mpegdemux/flumpegdemux.c b/gst/mpegdemux/flumpegdemux.c index a59e5175..c7392e24 100644 --- a/gst/mpegdemux/flumpegdemux.c +++ b/gst/mpegdemux/flumpegdemux.c @@ -47,6 +47,8 @@ #include "gstmpegdemux.h" #include "gstmpegtsdemux.h" +#include "mpegtsparse.h" +#include "mpegtspacketizer.h" GST_DEBUG_CATEGORY_EXTERN (gstflupesfilter_debug); GST_DEBUG_CATEGORY_EXTERN (gstflusectionfilter_debug); @@ -62,7 +64,8 @@ plugin_init (GstPlugin * plugin) return FALSE; if (!gst_fluts_demux_plugin_init (plugin)) return FALSE; - + if (!gst_mpegtsparse_plugin_init (plugin)) + return FALSE; return TRUE; } diff --git a/gst/mpegdemux/gstmpegdesc.c b/gst/mpegdemux/gstmpegdesc.c index 1bab07ba..727b3e8a 100644 --- a/gst/mpegdemux/gstmpegdesc.c +++ b/gst/mpegdemux/gstmpegdesc.c @@ -1,45 +1,33 @@ - /* - * This library is licensed under 2 different licenses and you - * can choose to use it under the terms of either one of them. The - * two licenses are the MPL 1.1 and the LGPL. - * - * MPL: - * - * The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/. - * - * Software distributed under the License is distributed on an "AS IS" - * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the - * License for the specific language governing rights and limitations - * under the License. - * - * LGPL: - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * - * The Original Code is Fluendo MPEG Demuxer plugin. - * - * The Initial Developer of the Original Code is Fluendo, S.L. - * Portions created by Fluendo, S.L. are Copyright (C) 2005 - * Fluendo, S.L. All Rights Reserved. - * - * Contributor(s): Wim Taymans - */ +/* + * 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 + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU Lesser General Public License Version 2 or later (the "LGPL"), + * in which case the provisions of the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of the MPL or the LGPL. + */ #include @@ -47,6 +35,9 @@ #include "gstmpegdesc.h" +GST_DEBUG_CATEGORY (gstmpegtsdesc_debug); +#define GST_CAT_DEFAULT (gstmpegtsdesc_debug) + void gst_mpeg_descriptor_free (GstMPEGDescriptor * desc) { @@ -149,6 +140,34 @@ gst_mpeg_descriptor_find (GstMPEGDescriptor * desc, gint tag) return NULL; } +/* array needs freeing afterwards */ +GArray * +gst_mpeg_descriptor_find_all (GstMPEGDescriptor * desc, gint tag) +{ + GArray *all; + + gint length; + guint8 *current; + guint size; + + g_return_val_if_fail (desc != NULL, NULL); + all = g_array_new (TRUE, TRUE, sizeof (guint8 *)); + + current = desc->data; + length = desc->data_length; + + while (length > 0) { + if (DESC_TAG (current) == tag) + g_array_append_val (all, current); + size = DESC_LENGTH (current) + 2; + + current += size; + length -= size; + } + + return all; +} + guint8 * gst_mpeg_descriptor_nth (GstMPEGDescriptor * desc, guint i) { @@ -173,6 +192,14 @@ gst_mpeg_descriptor_nth (GstMPEGDescriptor * desc, guint i) current += size; length -= size; i--; + } return NULL; } + +void +gst_mpegtsdesc_init_debug () +{ + GST_DEBUG_CATEGORY_INIT (gstmpegtsdesc_debug, "mpegtsdesc", 0, + "MPEG transport stream parser (descriptor)"); +} diff --git a/gst/mpegdemux/gstmpegdesc.h b/gst/mpegdemux/gstmpegdesc.h index 91a42217..12b8377d 100644 --- a/gst/mpegdemux/gstmpegdesc.h +++ b/gst/mpegdemux/gstmpegdesc.h @@ -1,10 +1,4 @@ -/* - * This library is licensed under 2 different licenses and you - * can choose to use it under the terms of either one of them. The - * two licenses are the MPL 1.1 and the LGPL. - * - * MPL: - * +/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at @@ -15,23 +9,6 @@ * License for the specific language governing rights and limitations * under the License. * - * LGPL: - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * * The Original Code is Fluendo MPEG Demuxer plugin. * * The Initial Developer of the Original Code is Fluendo, S.L. @@ -39,6 +16,17 @@ * Fluendo, S.L. All Rights Reserved. * * Contributor(s): Wim Taymans + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU Lesser General Public License Version 2 or later (the "LGPL"), + * in which case the provisions of the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of the MPL or the LGPL. */ #ifndef __GST_MPEG_DESC_H__ @@ -154,6 +142,7 @@ /* 0x7D and 0x7E are reserved for future use */ #define DESC_DVB_EXTENSION 0x7F /* 0x80 - 0xFE are user defined */ +#define DESC_DTG_LOGICAL_CHANNEL 0x83 /* from DTG D-Book */ /* 0xFF is forbidden */ /* common for all descriptors */ @@ -255,17 +244,78 @@ #define DESC_DVB_NETWORK_NAME_length(desc) (GST_READ_UINT8((desc)+1)) #define DESC_DVB_NETWORK_NAME_text(desc) (desc+2) +/* DVB Service Descriptor */ +#define DESC_DVB_SERVICE_type(desc) (desc[2]) +#define DESC_DVB_SERVICE_provider_name_length(desc) (desc[3]) +#define DESC_DVB_SERVICE_provider_name_text(desc) (desc+4) +#define DESC_DVB_SERVICE_name_length(desc) (desc[4 + DESC_DVB_SERVICE_provider_name_length(desc)]) +#define DESC_DVB_SERVICE_name_text(desc) (desc + 5 + DESC_DVB_SERVICE_provider_name_length(desc)) + +/* DVB Component Descriptor */ +#define DESC_DVB_COMPONENT_stream_content(desc) (desc[2] & 0x0F) +#define DESC_DVB_COMPONENT_type(desc) (desc[3]) +#define DESC_DVB_COMPONENT_tag(desc) (desc[4]) +#define DESC_DVB_COMPONENT_language(desc) (desc + 5) + +/* DVB Bouquet Name Descriptor */ +#define DESC_DVB_BOUQUET_NAME_text(desc) (desc + 2) + +/* DVB Short Event Descriptor */ +#define DESC_DVB_SHORT_EVENT_name_text(desc) (desc + 6) +#define DESC_DVB_SHORT_EVENT_name_length(desc) (desc[5]) +#define DESC_DVB_SHORT_EVENT_description_text(desc) (desc + 6 + DESC_DVB_SHORT_EVENT_name_length(desc) + 1) +#define DESC_DVB_SHORT_EVENT_description_length(desc) (desc[6 + DESC_DVB_SHORT_EVENT_name_length(desc)]) + +/* DVB Extended Event Descriptor */ +#define DESC_DVB_EXTENDED_EVENT_descriptor_number(desc) ((desc[2] & 0xF0) >> 4) +#define DESC_DVB_EXTENDED_EVENT_last_descriptor_number(desc) (desc[2] & 0x0F) +#define DESC_DVB_EXTENDED_EVENT_iso639_language_code(desc) (desc + 3) +#define DESC_DVB_EXTENDED_EVENT_items_length(desc) (desc[6]) +#define DESC_DVB_EXTENDED_EVENT_items(desc) (desc + 7) +#define DESC_DVB_EXTENDED_EVENT_text_length(desc) (desc[7 + DESC_DVB_EXTENDED_EVENT_items_length(desc)]) +#define DESC_DVB_EXTENDED_EVENT_text(desc) (desc + 7 + DESC_DVB_EXTENDED_EVENT_items_length(desc) + 1) + +/* DVB Satellite Delivery System Descriptor */ +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_frequency(desc) (desc + 2) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_orbital_position(desc) (desc + 6) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_west_east_flag(desc) ((desc[8] & 0x80) == 0x80) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_polarization(desc) ((desc[8] & 0x60) >> 5) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_modulation(desc) (desc[8] & 0x1F) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_symbol_rate(desc) (desc + 9) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_fec_inner(desc) (desc[12] & 0x0F) + +/* DVB Terrestrial Delivery System Descriptor */ +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_frequency(desc) (GST_READ_UINT32_BE((desc) + 2)) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_bandwidth(desc) (desc[6] & 0xE0) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_constellation(desc) (desc[7] & 0xC0) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_hierarchy(desc) (desc[7] & 0x38) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_code_rate_hp(desc) (desc[7] & 0x07) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_code_rate_lp(desc) (desc[8] & 0xE0) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_guard_interval(desc) (desc[8] & 0x18) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_transmission_mode(desc) (desc[8] & 0x06) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_other_frequency(desc) ((desc[8] & 0x01) == 0x01) + +/* DVB Cable Delivery System Descriptor */ +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_frequency(desc) (desc + 2) +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_fec_outer(desc) (desc[7] & 0x0F) +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_modulation(desc) (desc[8]) +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_symbol_rate(desc) (desc + 9) +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_fec_inner(desc) (desc[12] & 0x0F) + typedef struct { guint n_desc; guint8 data_length; guint8 *data; } GstMPEGDescriptor; +void gst_mpegtsdesc_init_debug (); GstMPEGDescriptor* gst_mpeg_descriptor_parse (guint8 *data, guint size); void gst_mpeg_descriptor_free (GstMPEGDescriptor *desc); guint gst_mpeg_descriptor_n_desc (GstMPEGDescriptor *desc); guint8* gst_mpeg_descriptor_find (GstMPEGDescriptor *desc, gint tag); +GArray* gst_mpeg_descriptor_find_all (GstMPEGDescriptor * desc, gint tag); + guint8* gst_mpeg_descriptor_nth (GstMPEGDescriptor *desc, guint i); #endif /* __GST_MPEG_DESC_H__ */ diff --git a/gst/mpegdemux/mpegtspacketizer.c b/gst/mpegdemux/mpegtspacketizer.c new file mode 100644 index 00000000..ce4a644a --- /dev/null +++ b/gst/mpegdemux/mpegtspacketizer.c @@ -0,0 +1,2234 @@ +/* + * mpegtspacketizer.c - + * Copyright (C) 2007, 2008 Alessandro Decina, Zaheer Merali + * + * Authors: + * Zaheer Merali + * Alessandro Decina + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "mpegtspacketizer.h" +#include "gstmpegdesc.h" + +GST_DEBUG_CATEGORY_STATIC (mpegts_packetizer_debug); +#define GST_CAT_DEFAULT mpegts_packetizer_debug + +G_DEFINE_TYPE (MpegTSPacketizer, mpegts_packetizer, G_TYPE_OBJECT); + +static void mpegts_packetizer_dispose (GObject * object); +static void mpegts_packetizer_finalize (GObject * object); +static gchar *convert_to_utf8 (const gchar * text, gint length, guint start, + const gchar * encoding, gboolean is_multibyte, GError ** error); +static gchar *get_encoding (const gchar * text, guint * start_text, + gboolean * is_multibyte); +static gchar *get_encoding_and_convert (const gchar * text, guint length); + +#define CONTINUITY_UNSET 255 +#define MAX_CONTINUITY 15 +#define VERSION_NUMBER_UNSET 255 +#define TABLE_ID_UNSET 0xFF + +static gint +mpegts_packetizer_stream_subtable_compare (gconstpointer a, gconstpointer b) +{ + MpegTSPacketizerStreamSubtable *asub, *bsub; + + asub = (MpegTSPacketizerStreamSubtable *) a; + bsub = (MpegTSPacketizerStreamSubtable *) b; + + if (asub->table_id == bsub->table_id && + asub->subtable_extension == bsub->subtable_extension) + return 0; + return -1; +} + +static MpegTSPacketizerStreamSubtable * +mpegts_packetizer_stream_subtable_new (guint8 table_id, + guint16 subtable_extension) +{ + MpegTSPacketizerStreamSubtable *subtable; + + subtable = g_new0 (MpegTSPacketizerStreamSubtable, 1); + subtable->version_number = VERSION_NUMBER_UNSET; + subtable->table_id = table_id; + subtable->subtable_extension = subtable_extension; + return subtable; +} + +static MpegTSPacketizerStream * +mpegts_packetizer_stream_new () +{ + MpegTSPacketizerStream *stream; + + stream = (MpegTSPacketizerStream *) g_new0 (MpegTSPacketizerStream, 1); + stream->section_adapter = gst_adapter_new (); + stream->continuity_counter = CONTINUITY_UNSET; + stream->subtables = NULL; + stream->section_table_id = TABLE_ID_UNSET; + return stream; +} + +static void +mpegts_packetizer_stream_free (MpegTSPacketizerStream * stream) +{ + gst_adapter_clear (stream->section_adapter); + g_object_unref (stream->section_adapter); + g_slist_foreach (stream->subtables, (GFunc) g_free, NULL); + g_slist_free (stream->subtables); + g_free (stream); +} + +static void +mpegts_packetizer_clear_section (MpegTSPacketizer * packetizer, + MpegTSPacketizerStream * stream) +{ + gst_adapter_clear (stream->section_adapter); + stream->continuity_counter = CONTINUITY_UNSET; + stream->section_length = 0; + stream->section_table_id = TABLE_ID_UNSET; +} + +static void +mpegts_packetizer_class_init (MpegTSPacketizerClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = mpegts_packetizer_dispose; + gobject_class->finalize = mpegts_packetizer_finalize; +} + +static void +mpegts_packetizer_init (MpegTSPacketizer * packetizer) +{ + packetizer->adapter = gst_adapter_new (); + packetizer->streams = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +static void +mpegts_packetizer_dispose (GObject * object) +{ + MpegTSPacketizer *packetizer = GST_MPEGTS_PACKETIZER (object); + + if (!packetizer->disposed) { + gst_adapter_clear (packetizer->adapter); + g_object_unref (packetizer->adapter); + packetizer->disposed = TRUE; + } + + if (G_OBJECT_CLASS (mpegts_packetizer_parent_class)->dispose) + G_OBJECT_CLASS (mpegts_packetizer_parent_class)->dispose (object); +} + +static gboolean +stream_foreach_remove (gpointer key, gpointer value, gpointer data) +{ + MpegTSPacketizerStream *stream; + + stream = (MpegTSPacketizerStream *) value; + mpegts_packetizer_stream_free (stream); + + return TRUE; +} + +static void +mpegts_packetizer_finalize (GObject * object) +{ + MpegTSPacketizer *packetizer = GST_MPEGTS_PACKETIZER (object); + + g_hash_table_foreach_remove (packetizer->streams, + stream_foreach_remove, packetizer); + g_hash_table_destroy (packetizer->streams); + + if (G_OBJECT_CLASS (mpegts_packetizer_parent_class)->finalize) + G_OBJECT_CLASS (mpegts_packetizer_parent_class)->finalize (object); +} + +static gboolean +mpegts_packetizer_parse_adaptation_field_control (MpegTSPacketizer * packetizer, + MpegTSPacketizerPacket * packet) +{ + guint8 length; + + length = *packet->data; + packet->data += 1; + + if (packet->adaptation_field_control == 0x02) { + /* no payload, adaptation field of 183 bytes */ + if (length != 183) { + GST_DEBUG ("PID %d afc == 0x%x and length %d != 183", + packet->pid, packet->adaptation_field_control, length); + } + } else if (length > 182) { + GST_DEBUG ("PID %d afc == 0x%01x and length %d > 182", + packet->pid, packet->adaptation_field_control, length); + } + + /* skip the adaptation field body for now */ + if (packet->data + length > packet->data_end) { + GST_DEBUG ("PID %d afc length %d overflows the buffer current %d max %d", + packet->pid, length, packet->data - packet->data_start, + packet->data_end - packet->data_start); + return FALSE; + } + + packet->data += length; + + return TRUE; +} + +static gboolean +mpegts_packetizer_parse_packet (MpegTSPacketizer * packetizer, + MpegTSPacketizerPacket * packet) +{ + guint8 *data; + + data = GST_BUFFER_DATA (packet->buffer); + /* skip sync_byte */ + data++; + + packet->payload_unit_start_indicator = (*data >> 6) & 0x01; + packet->pid = GST_READ_UINT16_BE (data) & 0x1FFF; + data += 2; + + packet->adaptation_field_control = (*data >> 4) & 0x03; + packet->continuity_counter = *data & 0x0F; + data += 1; + + packet->data = data; + + if (packet->adaptation_field_control & 0x02) + if (!mpegts_packetizer_parse_adaptation_field_control (packetizer, packet)) + return FALSE; + + if (packet->adaptation_field_control & 0x01) + packet->payload = packet->data; + else + packet->payload = NULL; + + return TRUE; +} + +static gboolean +mpegts_packetizer_parse_section_header (MpegTSPacketizer * packetizer, + MpegTSPacketizerStream * stream, MpegTSPacketizerSection * section) +{ + guint8 tmp; + guint8 *data; + MpegTSPacketizerStreamSubtable *subtable; + GSList *subtable_list = NULL; + + section->complete = TRUE; + /* get the section buffer, pass the ownership to the caller */ + section->buffer = gst_adapter_take_buffer (stream->section_adapter, + 3 + stream->section_length); + data = GST_BUFFER_DATA (section->buffer); + + section->table_id = *data++; + if ((data[0] & 0x80) == 0) + section->subtable_extension = 0; + else + section->subtable_extension = GST_READ_UINT16_BE (data + 2); + + subtable = mpegts_packetizer_stream_subtable_new (section->table_id, + section->subtable_extension); + + subtable_list = g_slist_find_custom (stream->subtables, subtable, + mpegts_packetizer_stream_subtable_compare); + if (subtable_list) { + g_free (subtable); + subtable = (MpegTSPacketizerStreamSubtable *) (subtable_list->data); + } else { + stream->subtables = g_slist_prepend (stream->subtables, subtable); + } + + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + /* skip to the version byte */ + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + if (!section->current_next_indicator) + goto not_applicable; + + if (section->version_number == subtable->version_number) + goto not_applicable; + subtable->version_number = section->version_number; + stream->section_table_id = section->table_id; + + return TRUE; + +not_applicable: + GST_LOG + ("not applicable pid %d table_id %d subtable_extension %d, current_next %d version %d", + section->pid, section->table_id, section->subtable_extension, + section->current_next_indicator, section->version_number); + section->complete = FALSE; + gst_buffer_unref (section->buffer); + return TRUE; +} + +static gboolean +mpegts_packetizer_parse_descriptors (MpegTSPacketizer * packetizer, + guint8 ** buffer, guint8 * buffer_end, GValueArray * descriptors) +{ + guint8 tag, length; + guint8 *data; + GValue value = { 0 }; + GString *desc; + + data = *buffer; + + while (data < buffer_end) { + tag = *data++; + length = *data++; + + if (data + length > buffer_end) { + GST_WARNING ("invalid descriptor length %d now at %d max %d", + length, data - *buffer, buffer_end - *buffer); + goto error; + } + + /* include tag and length */ + desc = g_string_new_len ((gchar *) data - 2, length + 2); + data += length; + /* G_TYPE_GSTING is a GBoxed type and is used so properly marshalled from python */ + g_value_init (&value, G_TYPE_GSTRING); + g_value_take_boxed (&value, desc); + g_value_array_append (descriptors, &value); + g_value_unset (&value); + } + + if (data != buffer_end) { + GST_WARNING ("descriptors size %d expected %d", + data - *buffer, buffer_end - *buffer); + goto error; + } + + *buffer = data; + + return TRUE; +error: + return FALSE; +} + +GstStructure * +mpegts_packetizer_parse_pat (MpegTSPacketizer * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *pat_info = NULL; + guint8 *data, *end; + guint transport_stream_id; + guint8 tmp; + guint program_number; + guint pmt_pid; + GValue entries = { 0 }; + GValue value = { 0 }; + GstStructure *entry = NULL; + gchar *struct_name; + + data = GST_BUFFER_DATA (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + transport_stream_id = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + pat_info = gst_structure_new ("pat", + "transport-stream-id", G_TYPE_UINT, transport_stream_id, NULL); + g_value_init (&entries, GST_TYPE_LIST); + /* stop at the CRC */ + end = GST_BUFFER_DATA (section->buffer) + GST_BUFFER_SIZE (section->buffer); + while (data < end - 4) { + program_number = GST_READ_UINT16_BE (data); + data += 2; + + pmt_pid = GST_READ_UINT16_BE (data) & 0x1FFF; + data += 2; + + struct_name = g_strdup_printf ("program-%d", program_number); + entry = gst_structure_new (struct_name, + "program-number", G_TYPE_UINT, program_number, + "pid", G_TYPE_UINT, pmt_pid, NULL); + g_free (struct_name); + + g_value_init (&value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&value, entry); + gst_value_list_append_value (&entries, &value); + g_value_unset (&value); + } + + gst_structure_set_value (pat_info, "programs", &entries); + g_value_unset (&entries); + + if (data != end - 4) { + /* FIXME: check the CRC before parsing the packet */ + GST_ERROR ("at the end of PAT data != end - 4"); + gst_structure_free (pat_info); + + return NULL; + } + + return pat_info; +} + +GstStructure * +mpegts_packetizer_parse_pmt (MpegTSPacketizer * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *pmt = NULL; + guint8 *data, *end; + guint16 program_number; + guint8 tmp; + guint pcr_pid; + guint program_info_length; + guint8 stream_type; + guint16 pid; + guint stream_info_length; + GValueArray *descriptors; + GValue stream_value = { 0 }; + GValue programs = { 0 }; + GstStructure *stream_info = NULL; + gchar *struct_name; + + /* fixed header + CRC == 16 */ + if (GST_BUFFER_SIZE (section->buffer) < 16) { + GST_WARNING ("PID %d invalid PMT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + program_number = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + pcr_pid = GST_READ_UINT16_BE (data) & 0x1FFF; + data += 2; + + program_info_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + struct_name = g_strdup ("pmt"); + pmt = gst_structure_new (struct_name, + "program-number", G_TYPE_UINT, program_number, + "pcr-pid", G_TYPE_UINT, pcr_pid, + "version-number", G_TYPE_UINT, section->version_number, NULL); + g_free (struct_name); + + if (program_info_length) { + /* check that the buffer is large enough to contain at least + * program_info_length bytes + CRC */ + if (data + program_info_length + 4 > end) { + GST_WARNING ("PID %d invalid program info length %d " + "left %d", section->pid, program_info_length, end - data); + goto error; + } + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + program_info_length, descriptors)) { + g_value_array_free (descriptors); + goto error; + } + + gst_structure_set (pmt, "descriptors", G_TYPE_VALUE_ARRAY, descriptors, + NULL); + g_value_array_free (descriptors); + } + + g_value_init (&programs, GST_TYPE_LIST); + /* parse entries, cycle until there's space for another entry (at least 5 + * bytes) plus the CRC */ + while (data <= end - 4 - 5) { + stream_type = *data++; + + pid = GST_READ_UINT16_BE (data) & 0x1FFF; + data += 2; + + stream_info_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + stream_info_length + 4 > end) { + GST_WARNING ("PID %d invalid stream info length %d " + "left %d", section->pid, stream_info_length, end - data); + g_value_unset (&programs); + goto error; + } + + struct_name = g_strdup_printf ("pid-%d", pid); + stream_info = gst_structure_new (struct_name, + "pid", G_TYPE_UINT, pid, "stream-type", G_TYPE_UINT, stream_type, NULL); + g_free (struct_name); + + if (stream_info_length) { + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + stream_info_length, descriptors)) { + g_value_unset (&programs); + gst_structure_free (stream_info); + g_value_array_free (descriptors); + goto error; + } + + gst_structure_set (stream_info, + "descriptors", G_TYPE_VALUE_ARRAY, descriptors, NULL); + g_value_array_free (descriptors); + } + + g_value_init (&stream_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&stream_value, stream_info); + gst_value_list_append_value (&programs, &stream_value); + g_value_unset (&stream_value); + } + + gst_structure_set_value (pmt, "streams", &programs); + g_value_unset (&programs); + + g_assert (data == end - 4); + + return pmt; + +error: + if (pmt) + gst_structure_free (pmt); + + return NULL; +} + +GstStructure * +mpegts_packetizer_parse_nit (MpegTSPacketizer * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *nit = NULL, *transport = NULL, *delivery_structure = NULL; + guint8 *data, *end, *entry_begin; + guint16 network_id, transport_stream_id, original_network_id; + guint tmp; + guint16 descriptors_loop_length, transport_stream_loop_length; + GValue transports = { 0 }; + GValue transport_value = { 0 }; + GValueArray *descriptors = NULL; + gchar *dbg_str; + + GST_DEBUG ("NIT"); + /* fixed header + CRC == 16 */ + if (GST_BUFFER_SIZE (section->buffer) < 23) { + GST_WARNING ("PID %d invalid NIT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + section->section_length != end) { + GST_WARNING ("PID %d invalid NIT section length %d expected %d", + section->pid, section->section_length, end - data); + goto error; + } + + network_id = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + nit = gst_structure_new ("nit", + "network-id", G_TYPE_UINT, network_id, + "version-number", G_TYPE_UINT, section->version_number, + "current-next-indicator", G_TYPE_UINT, section->current_next_indicator, + "actual-network", G_TYPE_BOOLEAN, section->table_id == 0x40, NULL); + + /* see if the buffer is large enough */ + if (descriptors_loop_length) { + guint8 *networkname_descriptor; + GstMPEGDescriptor *mpegdescriptor; + + if (data + descriptors_loop_length > end - 4) { + GST_WARNING ("PID %d invalid NIT descriptors loop length %d", + section->pid, descriptors_loop_length); + gst_structure_free (nit); + goto error; + } + mpegdescriptor = gst_mpeg_descriptor_parse (data, descriptors_loop_length); + networkname_descriptor = + gst_mpeg_descriptor_find (mpegdescriptor, DESC_DVB_NETWORK_NAME); + if (networkname_descriptor != NULL) { + gchar *networkname_tmp; + + /* No need to bounds check this value as it comes from the descriptor length itself */ + guint8 networkname_length = + DESC_DVB_NETWORK_NAME_length (networkname_descriptor); + gchar *networkname = + (gchar *) DESC_DVB_NETWORK_NAME_text (networkname_descriptor); + + networkname_tmp = + get_encoding_and_convert (networkname, networkname_length); + gst_structure_set (nit, "network-name", G_TYPE_STRING, networkname_tmp, + NULL); + g_free (networkname_tmp); + } + gst_mpeg_descriptor_free (mpegdescriptor); + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + descriptors_loop_length, descriptors)) { + gst_structure_free (nit); + g_value_array_free (descriptors); + goto error; + } + + gst_structure_set (nit, "descriptors", G_TYPE_VALUE_ARRAY, descriptors, + NULL); + g_value_array_free (descriptors); + } + + transport_stream_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + g_value_init (&transports, GST_TYPE_LIST); + /* read up to the CRC */ + while (transport_stream_loop_length - 4 > 0) { + gchar *transport_name; + + entry_begin = data; + + if (transport_stream_loop_length < 10) { + /* each entry must be at least 6 bytes (+ 4bytes CRC) */ + GST_WARNING ("PID %d invalid NIT entry size %d", + section->pid, transport_stream_loop_length); + goto error; + } + + transport_stream_id = GST_READ_UINT16_BE (data); + data += 2; + + original_network_id = GST_READ_UINT16_BE (data); + data += 2; + + descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + transport_name = g_strdup_printf ("transport-%d", transport_stream_id); + transport = gst_structure_new (transport_name, + "transport-stream-id", G_TYPE_UINT, transport_stream_id, + "original-network-id", G_TYPE_UINT, original_network_id, NULL); + g_free (transport_name); + + if (descriptors_loop_length) { + GstMPEGDescriptor *mpegdescriptor; + guint8 *delivery; + + if (data + descriptors_loop_length > end - 4) { + GST_WARNING ("PID %d invalid NIT entry %d descriptors loop length %d", + section->pid, transport_stream_id, descriptors_loop_length); + gst_structure_free (transport); + goto error; + } + mpegdescriptor = + gst_mpeg_descriptor_parse (data, descriptors_loop_length); + + if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DVB_SATELLITE_DELIVERY_SYSTEM))) { + + guint8 *frequency_bcd = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_frequency (delivery); + guint32 frequency = + 10 * ((frequency_bcd[3] & 0x0F) + + 10 * ((frequency_bcd[3] & 0xF0) >> 4) + + 100 * (frequency_bcd[2] & 0x0F) + + 1000 * ((frequency_bcd[2] & 0xF0) >> 4) + + 10000 * (frequency_bcd[1] & 0x0F) + + 100000 * ((frequency_bcd[1] & 0xF0) >> 4) + + 1000000 * (frequency_bcd[0] & 0x0F) + + 10000000 * ((frequency_bcd[0] & 0xF0) >> 4)); + guint8 *orbital_bcd = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_orbital_position (delivery); + gfloat orbital = + (orbital_bcd[1] & 0x0F) / 10. + ((orbital_bcd[1] & 0xF0) >> 4) + + 10 * (orbital_bcd[0] & 0x0F) + 100 * ((orbital_bcd[0] & 0xF0) >> 4); + gboolean east = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_west_east_flag (delivery); + guint8 polarization = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_polarization (delivery); + gchar *polarization_str; + guint8 modulation = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_modulation (delivery); + gchar *modulation_str; + guint8 *symbol_rate_bcd = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_symbol_rate (delivery); + guint32 symbol_rate = + (symbol_rate_bcd[2] & 0x0F) + + 10 * ((symbol_rate_bcd[2] & 0xF0) >> 4) + + 100 * (symbol_rate_bcd[1] & 0x0F) + + 1000 * ((symbol_rate_bcd[1] & 0xF0) >> 4) + + 10000 * (symbol_rate_bcd[0] & 0x0F) + + 100000 * ((symbol_rate_bcd[0] & 0xF0) >> 4); + guint8 fec_inner = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_fec_inner (delivery); + gchar *fec_inner_str; + + switch (polarization) { + case 0: + polarization_str = "horizontal"; + break; + case 1: + polarization_str = "vertical"; + break; + case 2: + polarization_str = "left"; + break; + case 3: + polarization_str = "right"; + break; + default: + polarization_str = ""; + } + switch (fec_inner) { + case 0: + fec_inner_str = "undefined"; + break; + case 1: + fec_inner_str = "1/2"; + break; + case 2: + fec_inner_str = "2/3"; + break; + case 3: + fec_inner_str = "3/4"; + break; + case 4: + fec_inner_str = "5/6"; + break; + case 5: + fec_inner_str = "7/8"; + break; + case 6: + fec_inner_str = "8/9"; + break; + case 0xF: + fec_inner_str = "none"; + break; + default: + fec_inner_str = "reserved"; + } + switch (modulation) { + case 0x00: + modulation_str = "undefined"; + break; + case 0x01: + modulation_str = "QAM16"; + break; + case 0x02: + modulation_str = "QAM32"; + break; + case 0x03: + modulation_str = "QAM64"; + break; + case 0x04: + modulation_str = "QAM128"; + break; + case 0x05: + modulation_str = "QAM256"; + break; + default: + modulation_str = "reserved"; + } + delivery_structure = gst_structure_new ("satellite", + "orbital", G_TYPE_FLOAT, orbital, + "east-or-west", G_TYPE_STRING, east ? "east" : "west", + "modulation", G_TYPE_STRING, modulation_str, + "frequency", G_TYPE_UINT, frequency, + "polarization", G_TYPE_STRING, polarization_str, + "symbol-rate", G_TYPE_UINT, symbol_rate, + "inner-fec", G_TYPE_STRING, fec_inner_str, NULL); + gst_structure_set (transport, "delivery", GST_TYPE_STRUCTURE, + delivery_structure, NULL); + } else if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM))) { + + guint32 frequency = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_frequency (delivery) * 10; + guint8 bandwidth = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_bandwidth (delivery); + guint8 constellation = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_constellation (delivery); + guint8 hierarchy = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_hierarchy (delivery); + guint8 code_rate_hp = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_code_rate_hp (delivery); + guint8 code_rate_lp = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_code_rate_lp (delivery); + guint8 guard_interval = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_guard_interval (delivery); + guint8 transmission_mode = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_transmission_mode (delivery); + gboolean other_frequency = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_other_frequency (delivery); + gchar *constellation_str, *code_rate_hp_str, *code_rate_lp_str, + *transmission_mode_str; + /* do the stuff */ + /* bandwidth is 8 if 0, 7 if 1, 6 if 2, reserved otherwise */ + if (bandwidth <= 2) + bandwidth = 8 - bandwidth; + else + bandwidth = 0; + switch (constellation) { + case 0: + constellation_str = "QPSK"; + break; + case 1: + constellation_str = "QAM16"; + break; + case 2: + constellation_str = "QAM64"; + break; + default: + constellation_str = "reserved"; + } + /* hierarchy is 4 if 3, 2 if 2, 1 if 1, 0 if 0, reserved if > 3 */ + if (hierarchy <= 3) { + if (hierarchy == 3) + hierarchy = 4; + } else { + hierarchy = 0; + } + + switch (code_rate_hp) { + case 0: + code_rate_hp_str = "1/2"; + break; + case 1: + code_rate_hp_str = "2/3"; + break; + case 2: + code_rate_hp_str = "3/4"; + break; + case 3: + code_rate_hp_str = "5/6"; + break; + case 4: + code_rate_hp_str = "7/8"; + break; + default: + code_rate_hp_str = "reserved"; + } + + switch (code_rate_lp) { + case 0: + code_rate_lp_str = "1/2"; + break; + case 1: + code_rate_lp_str = "2/3"; + break; + case 2: + code_rate_lp_str = "3/4"; + break; + case 3: + code_rate_lp_str = "5/6"; + break; + case 4: + code_rate_lp_str = "7/8"; + break; + default: + code_rate_lp_str = "reserved"; + } + /* guard is 32 if 0, 16 if 1, 8 if 2, 4 if 4 */ + switch (guard_interval) { + case 0: + guard_interval = 32; + break; + case 1: + guard_interval = 16; + break; + case 2: + guard_interval = 8; + break; + case 4: + guard_interval = 4; + break; + default: /* make it default to 32 */ + guard_interval = 32; + } + switch (transmission_mode) { + case 0: + transmission_mode_str = "2k"; + break; + case 1: + transmission_mode_str = "8k"; + break; + default: + transmission_mode_str = "reserved"; + } + delivery_structure = gst_structure_new ("terrestrial", + "frequency", G_TYPE_UINT, frequency, + "bandwidth", G_TYPE_UINT, bandwidth, + "constellation", G_TYPE_STRING, constellation_str, + "hierarchy", G_TYPE_UINT, hierarchy, + "code-rate-hp", G_TYPE_STRING, code_rate_hp_str, + "code-rate-lp", G_TYPE_STRING, code_rate_lp_str, + "guard-interval", G_TYPE_UINT, guard_interval, + "transmission-mode", G_TYPE_STRING, transmission_mode_str, + "other-frequency", G_TYPE_BOOLEAN, other_frequency, NULL); + gst_structure_set (transport, "delivery", GST_TYPE_STRUCTURE, + delivery_structure, NULL); + } else if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DVB_CABLE_DELIVERY_SYSTEM))) { + + guint8 *frequency_bcd = + DESC_DVB_CABLE_DELIVERY_SYSTEM_frequency (delivery); + /* see en 300 468 section 6.2.13.1 least significant bcd digit + * is measured in 100Hz units so multiplier needs to be 100 to get + * into Hz */ + guint32 frequency = 100 * + ((frequency_bcd[3] & 0x0F) + + 10 * ((frequency_bcd[3] & 0xF0) >> 4) + + 100 * (frequency_bcd[2] & 0x0F) + + 1000 * ((frequency_bcd[2] & 0xF0) >> 4) + + 10000 * (frequency_bcd[1] & 0x0F) + + 100000 * ((frequency_bcd[1] & 0xF0) >> 4) + + 1000000 * (frequency_bcd[0] & 0x0F) + + 10000000 * ((frequency_bcd[0] & 0xF0) >> 4)); + guint8 modulation = + DESC_DVB_CABLE_DELIVERY_SYSTEM_modulation (delivery); + gchar *modulation_str; + guint8 *symbol_rate_bcd = + DESC_DVB_CABLE_DELIVERY_SYSTEM_symbol_rate (delivery); + guint32 symbol_rate = + (symbol_rate_bcd[2] & 0x0F) + + 10 * ((symbol_rate_bcd[2] & 0xF0) >> 4) + + 100 * (symbol_rate_bcd[1] & 0x0F) + + 1000 * ((symbol_rate_bcd[1] & 0xF0) >> 4) + + 10000 * (symbol_rate_bcd[0] & 0x0F) + + 100000 * ((symbol_rate_bcd[0] & 0xF0) >> 4); + guint8 fec_inner = DESC_DVB_CABLE_DELIVERY_SYSTEM_fec_inner (delivery); + gchar *fec_inner_str; + + switch (fec_inner) { + case 0: + fec_inner_str = "undefined"; + break; + case 1: + fec_inner_str = "1/2"; + break; + case 2: + fec_inner_str = "2/3"; + break; + case 3: + fec_inner_str = "3/4"; + break; + case 4: + fec_inner_str = "5/6"; + break; + case 5: + fec_inner_str = "7/8"; + break; + case 6: + fec_inner_str = "8/9"; + break; + case 0xF: + fec_inner_str = "none"; + break; + default: + fec_inner_str = "reserved"; + } + switch (modulation) { + case 0x00: + modulation_str = "undefined"; + break; + case 0x01: + modulation_str = "QAM16"; + break; + case 0x02: + modulation_str = "QAM32"; + break; + case 0x03: + modulation_str = "QAM64"; + break; + case 0x04: + modulation_str = "QAM128"; + break; + case 0x05: + modulation_str = "QAM256"; + break; + default: + modulation_str = "reserved"; + } + delivery_structure = gst_structure_new ("cable", + "modulation", G_TYPE_STRING, modulation_str, + "frequency", G_TYPE_UINT, frequency, + "symbol-rate", G_TYPE_UINT, symbol_rate, + "inner-fec", G_TYPE_STRING, fec_inner_str, NULL); + gst_structure_set (transport, "delivery", GST_TYPE_STRUCTURE, + delivery_structure, NULL); + } + if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DTG_LOGICAL_CHANNEL))) { + guint8 *current_pos = delivery + 2; + GValue channel_numbers = { 0 }; + + g_value_init (&channel_numbers, GST_TYPE_LIST); + while (current_pos < delivery + DESC_LENGTH (delivery)) { + GstStructure *channel; + GValue channel_value = { 0 }; + guint16 service_id = GST_READ_UINT16_BE (current_pos); + guint16 logical_channel_number; + + current_pos += 2; + logical_channel_number = GST_READ_UINT16_BE (current_pos) & 0x03ff; + channel = + gst_structure_new ("channels", "service-id", G_TYPE_UINT, + service_id, "logical-channel-number", G_TYPE_UINT, + logical_channel_number, NULL); + g_value_init (&channel_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&channel_value, channel); + gst_value_list_append_value (&channel_numbers, &channel_value); + g_value_unset (&channel_value); + current_pos += 2; + } + gst_structure_set_value (transport, "channels", &channel_numbers); + g_value_unset (&channel_numbers); + } + gst_mpeg_descriptor_free (mpegdescriptor); + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + descriptors_loop_length, descriptors)) { + gst_structure_free (transport); + g_value_array_free (descriptors); + goto error; + } + + gst_structure_set (transport, "descriptors", G_TYPE_VALUE_ARRAY, + descriptors, NULL); + g_value_array_free (descriptors); + } + + g_value_init (&transport_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&transport_value, transport); + gst_value_list_append_value (&transports, &transport_value); + g_value_unset (&transport_value); + + transport_stream_loop_length -= data - entry_begin; + } + + if (data != end - 4) { + GST_WARNING ("PID %d invalid NIT parsed %d length %d", + section->pid, data - GST_BUFFER_DATA (section->buffer), + GST_BUFFER_SIZE (section->buffer)); + goto error; + } + + gst_structure_set_value (nit, "transports", &transports); + g_value_unset (&transports); + + dbg_str = gst_structure_to_string (nit); + GST_DEBUG ("NIT %s", dbg_str); + g_free (dbg_str); + + return nit; + +error: + if (nit) + gst_structure_free (nit); + + if (GST_VALUE_HOLDS_LIST (&transports)) + g_value_unset (&transports); + + return NULL; +} + +GstStructure * +mpegts_packetizer_parse_sdt (MpegTSPacketizer * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *sdt = NULL, *service = NULL; + guint8 *data, *end, *entry_begin; + guint16 transport_stream_id, original_network_id, service_id; + guint tmp; + guint sdt_info_length; + gboolean EIT_schedule, EIT_present_following; + guint8 running_status; + gboolean scrambled; + guint descriptors_loop_length; + GValue services = { 0 }; + GValueArray *descriptors = NULL; + GValue service_value = { 0 }; + gchar *dbg_str; + + GST_DEBUG ("SDT"); + /* fixed header + CRC == 16 */ + if (GST_BUFFER_SIZE (section->buffer) < 14) { + GST_WARNING ("PID %d invalid SDT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + section->section_length != end) { + GST_WARNING ("PID %d invalid SDT section length %d expected %d", + section->pid, section->section_length, end - data); + goto error; + } + + transport_stream_id = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + original_network_id = GST_READ_UINT16_BE (data); + data += 2; + + /* skip reserved byte */ + data += 1; + + sdt = gst_structure_new ("sdt", + "transport-stream-id", G_TYPE_UINT, transport_stream_id, + "version-number", G_TYPE_UINT, section->version_number, + "current-next-indicator", G_TYPE_UINT, section->current_next_indicator, + "original-network-id", G_TYPE_UINT, original_network_id, + "actual-transport-stream", G_TYPE_BOOLEAN, section->table_id == 0x42, + NULL); + + sdt_info_length = section->section_length - 8; + g_value_init (&services, GST_TYPE_LIST); + /* read up to the CRC */ + while (sdt_info_length - 4 > 0) { + gchar *service_name; + + entry_begin = data; + + if (sdt_info_length < 9) { + /* each entry must be at least 5 bytes (+4 bytes for the CRC) */ + GST_WARNING ("PID %d invalid SDT entry size %d", + section->pid, sdt_info_length); + goto error; + } + + service_id = GST_READ_UINT16_BE (data); + data += 2; + + /* reserved */ + data += 1; + + tmp = GST_READ_UINT16_BE (data); + data += 2; + + EIT_schedule = (tmp >> 15); + EIT_present_following = (tmp >> 14) & 0x01; + running_status = (tmp >> 5) & 0x03; + scrambled = (tmp >> 4) & 0x01; + descriptors_loop_length = tmp & 0x0FFF; + + /* TODO send tag event down relevant pad for channel name and provider */ + service_name = g_strdup_printf ("service-%d", service_id); + service = gst_structure_new (service_name, NULL); + g_free (service_name); + + if (descriptors_loop_length) { + guint8 *service_descriptor; + GstMPEGDescriptor *mpegdescriptor; + + if (data + descriptors_loop_length > end - 4) { + GST_WARNING ("PID %d invalid SDT entry %d descriptors loop length %d", + section->pid, service_id, descriptors_loop_length); + gst_structure_free (service); + goto error; + } + mpegdescriptor = + gst_mpeg_descriptor_parse (data, descriptors_loop_length); + service_descriptor = + gst_mpeg_descriptor_find (mpegdescriptor, DESC_DVB_SERVICE); + if (service_descriptor != NULL) { + gchar *servicename_tmp, *serviceprovider_name_tmp; + guint8 serviceprovider_name_length = + DESC_DVB_SERVICE_provider_name_length (service_descriptor); + gchar *serviceprovider_name = + (gchar *) DESC_DVB_SERVICE_provider_name_text (service_descriptor); + guint8 servicename_length = + DESC_DVB_SERVICE_name_length (service_descriptor); + gchar *servicename = + (gchar *) DESC_DVB_SERVICE_name_text (service_descriptor); + if (servicename_length + serviceprovider_name_length + 2 <= + DESC_LENGTH (service_descriptor)) { + + servicename_tmp = + get_encoding_and_convert (servicename, servicename_length); + serviceprovider_name_tmp = + get_encoding_and_convert (serviceprovider_name, + serviceprovider_name_length); + + gst_structure_set (service, "name", G_TYPE_STRING, servicename_tmp, + NULL); + gst_structure_set (service, "provider-name", G_TYPE_STRING, + serviceprovider_name_tmp, NULL); + g_free (servicename_tmp); + g_free (serviceprovider_name_tmp); + } + } + gst_mpeg_descriptor_free (mpegdescriptor); + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + descriptors_loop_length, descriptors)) { + gst_structure_free (service); + g_value_array_free (descriptors); + goto error; + } + + gst_structure_set (service, "descriptors", G_TYPE_VALUE_ARRAY, + descriptors, NULL); + + g_value_array_free (descriptors); + } + + g_value_init (&service_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&service_value, service); + gst_value_list_append_value (&services, &service_value); + g_value_unset (&service_value); + + sdt_info_length -= data - entry_begin; + } + + if (data != end - 4) { + GST_WARNING ("PID %d invalid SDT parsed %d length %d", + section->pid, data - GST_BUFFER_DATA (section->buffer), + GST_BUFFER_SIZE (section->buffer)); + goto error; + } + + gst_structure_set_value (sdt, "services", &services); + g_value_unset (&services); + + dbg_str = gst_structure_to_string (sdt); + g_free (dbg_str); + + return sdt; + +error: + if (sdt) + gst_structure_free (sdt); + + if (GST_VALUE_HOLDS_LIST (&services)) + g_value_unset (&services); + + return NULL; +} + +GstStructure * +mpegts_packetizer_parse_eit (MpegTSPacketizer * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *eit = NULL, *event = NULL; + guint service_id, last_table_id, segment_last_section_number; + guint transport_stream_id, original_network_id; + gboolean free_ca_mode; + guint event_id, running_status; + guint64 start_and_duration; + guint16 mjd; + guint year, month, day, hour, minute, second; + guint duration; + guint8 *data, *end, *duration_ptr, *utc_ptr; + guint16 descriptors_loop_length; + GValue events = { 0 }; + GValue event_value = { 0 }; + GValueArray *descriptors = NULL; + gchar *dbg_str, *event_name; + guint tmp; + + /* fixed header + CRC == 16 */ + if (GST_BUFFER_SIZE (section->buffer) < 18) { + GST_WARNING ("PID %d invalid EIT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + section->section_length != end) { + GST_WARNING ("PID %d invalid EIT section length %d expected %d", + section->pid, section->section_length, end - data); + goto error; + } + + service_id = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + transport_stream_id = GST_READ_UINT16_BE (data); + data += 2; + original_network_id = GST_READ_UINT16_BE (data); + data += 2; + segment_last_section_number = *data; + data += 1; + last_table_id = *data; + data += 1; + + eit = gst_structure_new ("eit", + "version-number", G_TYPE_UINT, section->version_number, + "current-next-indicator", G_TYPE_UINT, section->current_next_indicator, + "service-id", G_TYPE_UINT, service_id, + "actual-transport-stream", G_TYPE_BOOLEAN, (section->table_id == 0x4E || + (section->table_id >= 0x50 && section->table_id <= 0x5F)), + "present-following", G_TYPE_BOOLEAN, (section->table_id == 0x4E || + section->table_id == 0x4F), + "transport-stream-id", G_TYPE_UINT, transport_stream_id, + "original-network-id", G_TYPE_UINT, original_network_id, + "segment-last-section-number", G_TYPE_UINT, segment_last_section_number, + "last-table-id", G_TYPE_UINT, last_table_id, NULL); + + g_value_init (&events, GST_TYPE_LIST); + while (data < end - 4) { + /* 12 is the minimum entry size + CRC */ + if (end - data < 12 + 4) { + GST_WARNING ("PID %d invalid EIT entry length %d", + section->pid, end - 4 - data); + gst_structure_free (eit); + goto error; + } + + event_id = GST_READ_UINT16_BE (data); + data += 2; + start_and_duration = GST_READ_UINT64_BE (data); + duration_ptr = data + 5; + utc_ptr = data + 2; + mjd = GST_READ_UINT16_BE (data); + if (mjd == G_MAXUINT16) { + year = 1900; + month = day = hour = minute = second = 0; + } else { + /* See EN 300 468 Annex C */ + year = (guint32) (((mjd - 15078.2) / 365.25)); + month = (guint8) ((mjd - 14956.1 - (guint) (year * 365.25)) / 30.6001); + day = mjd - 14956 - (guint) (year * 365.25) - (guint) (month * 30.6001); + if (month == 14 || month == 15) { + year++; + month = month - 1 - 12; + } else { + month--; + } + year += 1900; + hour = ((utc_ptr[0] & 0xF0) >> 4) * 10 + (utc_ptr[0] & 0x0F); + minute = ((utc_ptr[1] & 0xF0) >> 4) * 10 + (utc_ptr[1] & 0x0F); + second = ((utc_ptr[2] & 0xF0) >> 4) * 10 + (utc_ptr[2] & 0x0F); + } + + duration = (((duration_ptr[0] & 0xF0) >> 4) * 10 + + (duration_ptr[0] & 0x0F)) * 60 * 60 + + (((duration_ptr[1] & 0xF0) >> 4) * 10 + + (duration_ptr[1] & 0x0F)) * 60 + + ((duration_ptr[2] & 0xF0) >> 4) * 10 + (duration_ptr[2] & 0x0F); + + data += 8; + running_status = *data >> 5; + free_ca_mode = (*data >> 4) & 0x01; + descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + /* TODO: send tag event down relevant pad saying what is currently playing */ + event_name = g_strdup_printf ("event-%d", event_id); + event = gst_structure_new (event_name, + "event-id", G_TYPE_UINT, event_id, + "year", G_TYPE_UINT, year, + "month", G_TYPE_UINT, month, + "day", G_TYPE_UINT, day, + "hour", G_TYPE_UINT, hour, + "minute", G_TYPE_UINT, minute, + "second", G_TYPE_UINT, second, + "duration", G_TYPE_UINT, duration, + "running-status", G_TYPE_UINT, running_status, + "free-ca-mode", G_TYPE_BOOLEAN, free_ca_mode, NULL); + g_free (event_name); + + if (descriptors_loop_length) { + guint8 *event_descriptor; + GArray *component_descriptors; + GArray *extended_event_descriptors; + GstMPEGDescriptor *mpegdescriptor; + + if (data + descriptors_loop_length > end - 4) { + GST_WARNING ("PID %d invalid EIT descriptors loop length %d", + section->pid, descriptors_loop_length); + gst_structure_free (event); + goto error; + } + mpegdescriptor = + gst_mpeg_descriptor_parse (data, descriptors_loop_length); + event_descriptor = + gst_mpeg_descriptor_find (mpegdescriptor, DESC_DVB_SHORT_EVENT); + if (event_descriptor != NULL) { + gchar *eventname_tmp, *eventdescription_tmp; + guint8 eventname_length = + DESC_DVB_SHORT_EVENT_name_length (event_descriptor); + gchar *eventname = + (gchar *) DESC_DVB_SHORT_EVENT_name_text (event_descriptor); + guint8 eventdescription_length = + DESC_DVB_SHORT_EVENT_description_length (event_descriptor); + gchar *eventdescription = + (gchar *) DESC_DVB_SHORT_EVENT_description_text (event_descriptor); + if (eventname_length + eventdescription_length + 2 <= + DESC_LENGTH (event_descriptor)) { + + eventname_tmp = + get_encoding_and_convert (eventname, eventname_length), + eventdescription_tmp = + get_encoding_and_convert (eventdescription, + eventdescription_length); + + gst_structure_set (event, "name", G_TYPE_STRING, eventname_tmp, NULL); + gst_structure_set (event, "description", G_TYPE_STRING, + eventdescription_tmp, NULL); + g_free (eventname_tmp); + g_free (eventdescription_tmp); + } + } + extended_event_descriptors = gst_mpeg_descriptor_find_all (mpegdescriptor, + DESC_DVB_EXTENDED_EVENT); + if (extended_event_descriptors) { + int i; + guint8 *extended_descriptor; + /*GValue extended_items = { 0 }; */ + gchar *extended_text = NULL; + gchar *extended_text_tmp; + /*g_value_init (&extended_items, GST_TYPE_LIST); */ + for (i = 0; i < extended_event_descriptors->len; i++) { + extended_descriptor = g_array_index (extended_event_descriptors, + guint8 *, i); + if (DESC_DVB_EXTENDED_EVENT_descriptor_number (extended_descriptor) == + i) { + if (extended_text) { + gchar *tmp; + gchar *old_extended_text = extended_text; + tmp = g_strndup ((gchar *) + DESC_DVB_EXTENDED_EVENT_text (extended_descriptor), + DESC_DVB_EXTENDED_EVENT_text_length (extended_descriptor)); + extended_text = g_strdup_printf ("%s%s", extended_text, tmp); + g_free (old_extended_text); + g_free (tmp); + } else { + extended_text = g_strndup ((gchar *) + DESC_DVB_EXTENDED_EVENT_text (extended_descriptor), + DESC_DVB_EXTENDED_EVENT_text_length (extended_descriptor)); + } + } + } + if (extended_text) { + extended_text_tmp = get_encoding_and_convert (extended_text, + strlen (extended_text)); + + gst_structure_set (event, "extended-text", G_TYPE_STRING, + extended_text_tmp, NULL); + g_free (extended_text_tmp); + g_free (extended_text); + } + g_array_free (extended_event_descriptors, TRUE); + } + + component_descriptors = gst_mpeg_descriptor_find_all (mpegdescriptor, + DESC_DVB_COMPONENT); + if (component_descriptors) { + int i; + guint8 *comp_descriptor; + GValue components = { 0 }; + g_value_init (&components, GST_TYPE_LIST); + /* FIXME: do the component descriptor parsing less verbosely + * and better...a task for 0.10.6 */ + for (i = 0; i < component_descriptors->len; i++) { + GstStructure *component = NULL; + GValue component_value = { 0 }; + gint widescreen = 0; /* 0 for 4:3, 1 for 16:9, 2 for > 16:9 */ + gint freq = 25; /* 25 or 30 measured in Hertz */ + gboolean highdef = FALSE; + gboolean panvectors = FALSE; + gchar *comptype = ""; + + comp_descriptor = g_array_index (component_descriptors, guint8 *, i); + switch (DESC_DVB_COMPONENT_stream_content (comp_descriptor)) { + case 0x01: + /* video */ + switch (DESC_DVB_COMPONENT_type (comp_descriptor)) { + case 0x01: + widescreen = 0; + freq = 25; + break; + case 0x02: + widescreen = 1; + panvectors = TRUE; + freq = 25; + break; + case 0x03: + widescreen = 1; + panvectors = FALSE; + freq = 25; + break; + case 0x04: + widescreen = 2; + freq = 25; + break; + case 0x05: + widescreen = 0; + freq = 30; + break; + case 0x06: + widescreen = 1; + panvectors = TRUE; + freq = 30; + break; + case 0x07: + widescreen = 1; + panvectors = FALSE; + freq = 30; + break; + case 0x08: + widescreen = 2; + freq = 30; + break; + case 0x09: + widescreen = 0; + highdef = TRUE; + freq = 25; + break; + case 0x0A: + widescreen = 1; + highdef = TRUE; + panvectors = TRUE; + freq = 25; + break; + case 0x0B: + widescreen = 1; + highdef = TRUE; + panvectors = FALSE; + freq = 25; + break; + case 0x0C: + widescreen = 2; + highdef = TRUE; + freq = 25; + break; + case 0x0D: + widescreen = 0; + highdef = TRUE; + freq = 30; + break; + case 0x0E: + widescreen = 1; + highdef = TRUE; + panvectors = TRUE; + freq = 30; + break; + case 0x0F: + widescreen = 1; + highdef = TRUE; + panvectors = FALSE; + freq = 30; + break; + case 0x10: + widescreen = 2; + highdef = TRUE; + freq = 30; + break; + } + component = gst_structure_new ("video", "high-definition", + G_TYPE_BOOLEAN, TRUE, "frequency", G_TYPE_INT, freq, NULL); + if (widescreen == 0) { + gst_structure_set (component, "aspect-ratio", + G_TYPE_STRING, "4:3", NULL); + } else if (widescreen == 2) { + gst_structure_set (component, "aspect-ratio", G_TYPE_STRING, + "> 16:9", NULL); + } else { + gst_structure_set (component, "aspect-ratio", G_TYPE_STRING, + "16:9", "pan-vectors", G_TYPE_BOOLEAN, panvectors, NULL); + } + break; + case 0x02: /* audio */ + comptype = "undefined"; + switch (DESC_DVB_COMPONENT_type (comp_descriptor)) { + case 0x01: + comptype = "single channel mono"; + break; + case 0x02: + comptype = "dual channel mono"; + break; + case 0x03: + comptype = "stereo"; + break; + case 0x04: + comptype = "multi-channel multi-lingual"; + break; + case 0x05: + comptype = "surround"; + break; + case 0x40: + comptype = "audio description for the visually impaired"; + break; + case 0x41: + comptype = "audio for the hard of hearing"; + break; + } + component = gst_structure_new ("audio", "type", G_TYPE_STRING, + comptype, NULL); + break; + case 0x03: /* subtitles/teletext/vbi */ + comptype = "reserved"; + switch (DESC_DVB_COMPONENT_type (comp_descriptor)) { + case 0x01: + comptype = "EBU Teletext subtitles"; + break; + case 0x02: + comptype = "associated EBU Teletext"; + break; + case 0x03: + comptype = "VBI data"; + break; + case 0x10: + comptype = "Normal DVB subtitles"; + break; + case 0x11: + comptype = "Normal DVB subtitles for 4:3"; + break; + case 0x12: + comptype = "Normal DVB subtitles for 16:9"; + break; + case 0x13: + comptype = "Normal DVB subtitles for 2.21:1"; + break; + case 0x20: + comptype = "Hard of hearing DVB subtitles"; + break; + case 0x21: + comptype = "Hard of hearing DVB subtitles for 4:3"; + break; + case 0x22: + comptype = "Hard of hearing DVB subtitles for 16:9"; + break; + case 0x23: + comptype = "Hard of hearing DVB subtitles for 2.21:1"; + break; + } + component = gst_structure_new ("teletext", "type", G_TYPE_STRING, + comptype, NULL); + break; + } + if (component) { + g_value_init (&component_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&component_value, component); + gst_value_list_append_value (&components, &component_value); + g_value_unset (&component_value); + component = NULL; + } + } + gst_structure_set_value (event, "components", &components); + g_array_free (component_descriptors, TRUE); + } + gst_mpeg_descriptor_free (mpegdescriptor); + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + descriptors_loop_length, descriptors)) { + gst_structure_free (event); + g_value_array_free (descriptors); + goto error; + } + gst_structure_set (event, "descriptors", G_TYPE_VALUE_ARRAY, descriptors, + NULL); + g_value_array_free (descriptors); + } + + g_value_init (&event_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&event_value, event); + gst_value_list_append_value (&events, &event_value); + g_value_unset (&event_value); + } + + if (data != end - 4) { + GST_WARNING ("PID %d invalid EIT parsed %d length %d", + section->pid, data - GST_BUFFER_DATA (section->buffer), + GST_BUFFER_SIZE (section->buffer)); + goto error; + } + + gst_structure_set_value (eit, "events", &events); + g_value_unset (&events); + + dbg_str = gst_structure_to_string (eit); + GST_DEBUG ("EIT %s", dbg_str); + g_free (dbg_str); + + return eit; + +error: + if (eit) + gst_structure_free (eit); + + if (GST_VALUE_HOLDS_LIST (&events)) + g_value_unset (&events); + + return NULL; +} + +static void +foreach_stream_clear (gpointer key, gpointer value, gpointer data) +{ + MpegTSPacketizerStream *stream = (MpegTSPacketizerStream *) value; + + /* remove the stream */ + g_object_unref (stream->section_adapter); + g_free (stream); +} + +static gboolean +remove_all (gpointer key, gpointer value, gpointer user_data) +{ + return TRUE; +} + +void +mpegts_packetizer_clear (MpegTSPacketizer * packetizer) +{ + g_hash_table_foreach (packetizer->streams, foreach_stream_clear, packetizer); + + /* FIXME can't use remove_all because we don't depend on 2.12 yet */ + g_hash_table_foreach_remove (packetizer->streams, remove_all, NULL); + gst_adapter_clear (packetizer->adapter); +} + +MpegTSPacketizer * +mpegts_packetizer_new () +{ + MpegTSPacketizer *packetizer; + + packetizer = + GST_MPEGTS_PACKETIZER (g_object_new (GST_TYPE_MPEGTS_PACKETIZER, NULL)); + + return packetizer; +} + +void +mpegts_packetizer_push (MpegTSPacketizer * packetizer, GstBuffer * buffer) +{ + g_return_if_fail (GST_IS_MPEGTS_PACKETIZER (packetizer)); + g_return_if_fail (GST_IS_BUFFER (buffer)); + + gst_adapter_push (packetizer->adapter, buffer); +} + +gboolean +mpegts_packetizer_has_packets (MpegTSPacketizer * packetizer) +{ + g_return_val_if_fail (GST_IS_MPEGTS_PACKETIZER (packetizer), FALSE); + + return gst_adapter_available (packetizer->adapter) >= 188; +} + +gboolean +mpegts_packetizer_next_packet (MpegTSPacketizer * packetizer, + MpegTSPacketizerPacket * packet) +{ + guint8 sync_byte; + gboolean ret = FALSE; + + g_return_val_if_fail (GST_IS_MPEGTS_PACKETIZER (packetizer), FALSE); + g_return_val_if_fail (packet != NULL, FALSE); + + packet->buffer = NULL; + while (gst_adapter_available (packetizer->adapter) >= 188) { + sync_byte = *gst_adapter_peek (packetizer->adapter, 1); + if (sync_byte != 0x47) { + GST_DEBUG ("lost sync %02x", sync_byte); + gst_adapter_flush (packetizer->adapter, 1); + continue; + } + + packet->buffer = gst_adapter_take_buffer (packetizer->adapter, 188); + packet->data_start = GST_BUFFER_DATA (packet->buffer); + packet->data_end = + GST_BUFFER_DATA (packet->buffer) + GST_BUFFER_SIZE (packet->buffer); + ret = mpegts_packetizer_parse_packet (packetizer, packet); + break; + } + + return ret; +} + +void +mpegts_packetizer_clear_packet (MpegTSPacketizer * packetizer, + MpegTSPacketizerPacket * packet) +{ + g_return_if_fail (GST_IS_MPEGTS_PACKETIZER (packetizer)); + g_return_if_fail (packet != NULL); + + if (packet->buffer) + gst_buffer_unref (packet->buffer); + packet->buffer = NULL; + packet->continuity_counter = 0; + packet->payload_unit_start_indicator = 0; + packet->payload = NULL; + packet->data_start = NULL; + packet->data_end = NULL; +} + +gboolean +mpegts_packetizer_push_section (MpegTSPacketizer * packetizer, + MpegTSPacketizerPacket * packet, MpegTSPacketizerSection * section) +{ + gboolean res = FALSE; + MpegTSPacketizerStream *stream; + guint8 pointer, table_id; + guint16 subtable_extension; + guint section_length; + GstBuffer *sub_buf; + guint8 *data; + + g_return_val_if_fail (GST_IS_MPEGTS_PACKETIZER (packetizer), FALSE); + g_return_val_if_fail (packet != NULL, FALSE); + g_return_val_if_fail (section != NULL, FALSE); + + data = packet->data; + section->pid = packet->pid; + + if (packet->payload_unit_start_indicator == 1) { + pointer = *data++; + if (data + pointer > packet->data_end) { + GST_WARNING ("PID %d PSI section pointer points past the end " + "of the buffer", packet->pid); + goto out; + } + + data += pointer; + } + /* create a sub buffer from the start of the section (table_id and + * section_length included) to the end */ + sub_buf = gst_buffer_create_sub (packet->buffer, + data - GST_BUFFER_DATA (packet->buffer), packet->data_end - data); + + stream = (MpegTSPacketizerStream *) g_hash_table_lookup (packetizer->streams, + GINT_TO_POINTER ((gint) packet->pid)); + if (stream == NULL) { + stream = mpegts_packetizer_stream_new (); + g_hash_table_insert (packetizer->streams, + GINT_TO_POINTER ((gint) packet->pid), stream); + } + + if (packet->payload_unit_start_indicator) { + table_id = *data++; + /* subtable_extension should be read from 4th and 5th bytes only if + * section_syntax_indicator is 1 */ + if ((data[0] & 0x80) == 0) + subtable_extension = 0; + else + subtable_extension = GST_READ_UINT16_BE (data + 2); + GST_DEBUG ("pid: %d table_id %d sub_table_extension %d", + packet->pid, table_id, subtable_extension); + + section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + + if (stream->continuity_counter != CONTINUITY_UNSET) { + GST_DEBUG + ("PID %d table_id %d sub_table_extension %d payload_unit_start_indicator set but section " + "not complete (last_continuity: %d continuity: %d sec len %d buffer %d avail %d", + packet->pid, table_id, subtable_extension, stream->continuity_counter, + packet->continuity_counter, section_length, GST_BUFFER_SIZE (sub_buf), + gst_adapter_available (stream->section_adapter)); + mpegts_packetizer_clear_section (packetizer, stream); + } else { + GST_DEBUG + ("pusi set and new stream section is %d long and data we have is: %d", + section_length, packet->data_end - packet->data); + } + stream->continuity_counter = packet->continuity_counter; + stream->section_length = section_length; + stream->section_table_id = table_id; + gst_adapter_push (stream->section_adapter, sub_buf); + + res = TRUE; + } else if (stream->continuity_counter != CONTINUITY_UNSET && + (packet->continuity_counter == stream->continuity_counter + 1 || + (stream->continuity_counter == MAX_CONTINUITY && + packet->continuity_counter == 0))) { + stream->continuity_counter = packet->continuity_counter; + gst_adapter_push (stream->section_adapter, sub_buf); + + res = TRUE; + } else { + if (stream->continuity_counter == CONTINUITY_UNSET) + GST_DEBUG ("PID %d waiting for pusi", packet->pid); + else + GST_DEBUG ("PID %d section discontinuity " + "(last_continuity: %d continuity: %d", packet->pid, + stream->continuity_counter, packet->continuity_counter); + mpegts_packetizer_clear_section (packetizer, stream); + gst_buffer_unref (sub_buf); + } + + if (res) { + /* we pushed some data in the section adapter, see if the section is + * complete now */ + + /* >= as sections can be padded and padding is not included in + * section_length */ + if (gst_adapter_available (stream->section_adapter) >= + stream->section_length + 3) { + res = mpegts_packetizer_parse_section_header (packetizer, + stream, section); + + /* flush stuffing bytes */ + mpegts_packetizer_clear_section (packetizer, stream); + } else { + /* section not complete yet */ + section->complete = FALSE; + } + } else { + GST_WARNING ("section not complete"); + section->complete = FALSE; + } + +out: + packet->data = data; + return res; +} + +void +mpegts_packetizer_init_debug () +{ + GST_DEBUG_CATEGORY_INIT (mpegts_packetizer_debug, "mpegtspacketizer", 0, + "MPEG transport stream parser"); +} + +/** + * @text: The text you want to get the encoding from + * @start_text: Location where the beginning of the actual text is stored + * @is_multibyte: Location where information whether it's a multibyte encoding + * or not is stored + * @returns: Name of encoding or NULL of encoding could not be detected. + * + * The returned string should be freed with g_free () when no longer needed. + */ +static gchar * +get_encoding (const gchar * text, guint * start_text, gboolean * is_multibyte) +{ + gchar *encoding; + guint8 firstbyte; + + g_return_val_if_fail (text != NULL, NULL); + + firstbyte = (guint8) text[0]; + + if (firstbyte == 0x01) { + encoding = g_strdup ("iso8859-5"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte == 0x02) { + encoding = g_strdup ("iso8859-6"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte == 0x03) { + encoding = g_strdup ("iso8859-7"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte == 0x04) { + encoding = g_strdup ("iso8859-8"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte == 0x05) { + encoding = g_strdup ("iso8859-9"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte >= 0x20) { + encoding = g_strdup ("iso6937"); + *start_text = 0; + *is_multibyte = FALSE; + } else if (firstbyte == 0x10) { + guint16 table; + gchar table_str[6]; + + text++; + table = GST_READ_UINT16_BE (text); + g_snprintf (table_str, 6, "%d", table); + + encoding = g_strconcat ("iso8859-", table_str, NULL); + *start_text = 3; + *is_multibyte = FALSE; + } else if (firstbyte == 0x11) { + encoding = g_strdup ("ISO-10646/UCS2"); + *start_text = 1; + *is_multibyte = TRUE; + } else if (firstbyte == 0x12) { + // That's korean encoding. + // The spec says it's encoded in KSC 5601, but iconv only knows KSC 5636. + // Couldn't find any information about either of them. + encoding = NULL; + *start_text = 1; + *is_multibyte = TRUE; + } else { + // reserved + encoding = NULL; + } + + return encoding; +} + +/** + * @text: The text to convert. It may include pango markup ( and ) + * @length: The length of the string -1 if it's nul-terminated + * @start: Where to start converting in the text + * @encoding: The encoding of text + * @is_multibyte: Whether the encoding is a multibyte encoding + * @error: The location to store the error, or NULL to ignore errors + * @returns: UTF-8 encoded string + * + * Convert text to UTF-8. + */ +static gchar * +convert_to_utf8 (const gchar * text, gint length, guint start, + const gchar * encoding, gboolean is_multibyte, GError ** error) +{ + gchar *new_text; + GByteArray *sb; + gint i; + + g_return_val_if_fail (text != NULL, NULL); + g_return_val_if_fail (encoding != NULL, NULL); + + text += start; + + sb = g_byte_array_sized_new (length * 1.1); + + if (is_multibyte) { + if (length == -1) { + while (*text != '\0') { + guint16 code = GST_READ_UINT16_BE (text); + + switch (code) { + case 0xE086:{ + guint8 emph_on[] = { 0x3C, 0x00, // < + 0x62, 0x00, // b + 0x3E, 0x00 // > + }; + g_byte_array_append (sb, emph_on, 6); + break; + } + case 0xE087:{ + guint8 emph_on[] = { 0x3C, 0x00, // < + 0x2F, 0x00, // / + 0x62, 0x00, // b + 0x3E, 0x00 // > + }; + g_byte_array_append (sb, emph_on, 8); + break; + } + case 0xE08A:{ + guint8 nl[] = { 0x0A, 0x00 }; // new line + g_byte_array_append (sb, nl, 2); + break; + } + default: + g_byte_array_append (sb, (guint8 *) text, 2); + break; + } + + text += 2; + } + } else { + for (i = 0; i < length; i += 2) { + guint16 code = GST_READ_UINT16_BE (text); + + switch (code) { + case 0xE086:{ + guint8 emph_on[] = { 0x3C, 0x00, // < + 0x62, 0x00, // b + 0x3E, 0x00 // > + }; + g_byte_array_append (sb, emph_on, 6); + break; + } + case 0xE087:{ + guint8 emph_on[] = { 0x3C, 0x00, // < + 0x2F, 0x00, // / + 0x62, 0x00, // b + 0x3E, 0x00 // > + }; + g_byte_array_append (sb, emph_on, 8); + break; + } + case 0xE08A:{ + guint8 nl[] = { 0x0A, 0x00 }; // new line + g_byte_array_append (sb, nl, 2); + break; + } + default: + g_byte_array_append (sb, (guint8 *) text, 2); + break; + } + + text += 2; + } + } + } else { + if (length == -1) { + while (*text != '\0') { + guint8 code = (guint8) (*text); + + switch (code) { + case 0x86: + g_byte_array_append (sb, (guint8 *) "", 3); + break; + case 0x87: + g_byte_array_append (sb, (guint8 *) "", 4); + break; + case 0x8A: + g_byte_array_append (sb, (guint8 *) "\n", 1); + break; + default: + g_byte_array_append (sb, &code, 1); + break; + } + + text++; + } + } else { + for (i = 0; i < length; i++) { + guint8 code = (guint8) (*text); + + switch (code) { + case 0x86: + g_byte_array_append (sb, (guint8 *) "", 3); + break; + case 0x87: + g_byte_array_append (sb, (guint8 *) "", 4); + break; + case 0x8A: + g_byte_array_append (sb, (guint8 *) "\n", 1); + break; + default: + g_byte_array_append (sb, &code, 1); + break; + } + + text++; + } + } + } + + if (sb->len > 0) { + new_text = + g_convert ((gchar *) sb->data, sb->len, "utf-8", encoding, NULL, NULL, + error); + } else { + new_text = g_strdup (""); + } + + g_byte_array_free (sb, TRUE); + + return new_text; +} + +static gchar * +get_encoding_and_convert (const gchar * text, guint length) +{ + GError *error = NULL; + gchar *converted_str; + gchar *encoding; + guint start_text = 0; + gboolean is_multibyte; + + g_return_val_if_fail (text != NULL, NULL); + + encoding = get_encoding (text, &start_text, &is_multibyte); + + if (encoding == NULL) { + converted_str = g_strndup (text, length); + } else { + converted_str = convert_to_utf8 (text, length - start_text, start_text, + encoding, is_multibyte, &error); + if (error != NULL) { + g_critical ("Could not convert string: %s", error->message); + g_error_free (error); + text += start_text; + converted_str = g_strndup (text, length - start_text); + } + + g_free (encoding); + } + + return converted_str; +} diff --git a/gst/mpegdemux/mpegtspacketizer.h b/gst/mpegdemux/mpegtspacketizer.h new file mode 100644 index 00000000..c54228af --- /dev/null +++ b/gst/mpegdemux/mpegtspacketizer.h @@ -0,0 +1,135 @@ +/* + * mpegtspacketizer.h - + * Copyright (C) 2007 Alessandro Decina + * + * Authors: + * Alessandro Decina + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_MPEGTS_PACKETIZER_H +#define GST_MPEGTS_PACKETIZER_H + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_MPEGTS_PACKETIZER \ + (mpegts_packetizer_get_type()) +#define GST_MPEGTS_PACKETIZER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MPEGTS_PACKETIZER,MpegTSPacketizer)) +#define GST_MPEGTS_PACKETIZER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MPEGTS_PACKETIZER,MpegTSPacketizerClass)) +#define GST_IS_MPEGTS_PACKETIZER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MPEGTS_PACKETIZER)) +#define GST_IS_MPEGTS_PACKETIZER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MPEGTS_PACKETIZER)) + + +typedef struct _MpegTSPacketizer MpegTSPacketizer; +typedef struct _MpegTSPacketizerClass MpegTSPacketizerClass; + +struct _MpegTSPacketizer { + GObject object; + + GstAdapter *adapter; + /* streams hashed by pid */ + GHashTable *streams; + gboolean disposed; +}; + +struct _MpegTSPacketizerClass { + GObjectClass object_class; +}; + +typedef struct +{ + GstBuffer *buffer; + gint16 pid; + guint8 payload_unit_start_indicator; + guint8 adaptation_field_control; + guint8 continuity_counter; + guint8 *payload; + + guint8 *data_start; + guint8 *data_end; + guint8 *data; + +} MpegTSPacketizerPacket; + +typedef struct +{ + gboolean complete; + GstBuffer *buffer; + gint16 pid; + guint8 table_id; + guint16 subtable_extension; + guint section_length; + guint8 version_number; + guint8 current_next_indicator; +} MpegTSPacketizerSection; + +typedef struct +{ + guint8 table_id; + /* the spec says sub_table_extension is the fourth and fifth byte of a + * section when the section_syntax_indicator is set to a value of "1". If + * section_syntax_indicator is 0, sub_table_extension will be set to 0 */ + guint16 subtable_extension; + guint8 version_number; +} MpegTSPacketizerStreamSubtable; + +typedef struct +{ + guint continuity_counter; + GstAdapter *section_adapter; + guint8 section_table_id; + guint section_length; + GSList *subtables; +} MpegTSPacketizerStream; + + +GType gst_mpegts_packetizer_get_type(void); + +void mpegts_packetizer_init_debug (); +MpegTSPacketizer *mpegts_packetizer_new (); +void mpegts_packetizer_clear (MpegTSPacketizer *packetizer); +void mpegts_packetizer_push (MpegTSPacketizer *packetizer, GstBuffer *buffer); +gboolean mpegts_packetizer_has_packets (MpegTSPacketizer *packetizer); +gboolean mpegts_packetizer_next_packet (MpegTSPacketizer *packetizer, + MpegTSPacketizerPacket *packet); +void mpegts_packetizer_clear_packet (MpegTSPacketizer *packetizer, + MpegTSPacketizerPacket *packet); + +gboolean mpegts_packetizer_push_section (MpegTSPacketizer *packetzer, + MpegTSPacketizerPacket *packet, MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_pat (MpegTSPacketizer *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_pmt (MpegTSPacketizer *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_nit (MpegTSPacketizer *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_sdt (MpegTSPacketizer *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_eit (MpegTSPacketizer *packetizer, + MpegTSPacketizerSection *section); + +G_END_DECLS + +#endif /* GST_MPEGTS_PACKETIZER_H */ diff --git a/gst/mpegdemux/mpegtsparse.c b/gst/mpegdemux/mpegtsparse.c new file mode 100644 index 00000000..18a6b435 --- /dev/null +++ b/gst/mpegdemux/mpegtsparse.c @@ -0,0 +1,1293 @@ +/* + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "mpegtsparse.h" +#include "gstmpegdesc.h" + +/* latency in mseconds */ +#define TS_LATENCY 700 + +#define TABLE_ID_UNSET 0xFF + +GST_DEBUG_CATEGORY_STATIC (mpegts_parse_debug); +#define GST_CAT_DEFAULT mpegts_parse_debug + +typedef struct _MpegTSParsePad MpegTSParsePad; + +typedef struct +{ + guint16 pid; + guint8 stream_type; +} MpegTSParseStream; + +typedef struct +{ + gint program_number; + guint16 pmt_pid; + guint16 pcr_pid; + GstStructure *pmt_info; + GHashTable *streams; + gint patcount; + gint selected; + gboolean active; + 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 GstElementDetails mpegts_parse_details = +GST_ELEMENT_DETAILS ("MPEG transport stream parser", + "Codec/Parser", + "Parses MPEG2 transport streams", + "Alessandro Decina \n" + "Zaheer Abbas Merali "); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ") + ); + +static GstStaticPadTemplate src_template = +GST_STATIC_PAD_TEMPLATE ("src%d", GST_PAD_SRC, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ") + ); + +static GstStaticPadTemplate program_template = +GST_STATIC_PAD_TEMPLATE ("program_%d", GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ") + ); + +enum +{ + ARG_0, + PROP_PROGRAM_NUMBERS, + /* 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_dispose (GObject * object); +static void mpegts_parse_finalize (GObject * object); + +static MpegTSParsePad *mpegts_parse_create_tspad (MpegTSParse * parse, + const gchar * name); +static void mpegts_parse_destroy_tspad (MpegTSParse * parse, + MpegTSParsePad * tspad); +static GstPad *mpegts_parse_activate_program (MpegTSParse * parse, + MpegTSParseProgram * program); +static void mpegts_parse_free_program (MpegTSParseProgram * program); +static void mpegts_parse_free_stream (MpegTSParseStream * ptream); +static void mpegts_parse_reset_selected_programs (MpegTSParse * parse, + gchar * programs); + +static void mpegts_parse_pad_removed (GstElement * element, GstPad * pad); +static GstPad *mpegts_parse_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static void mpegts_parse_release_pad (GstElement * element, GstPad * pad); +static GstFlowReturn mpegts_parse_chain (GstPad * pad, GstBuffer * buf); +static gboolean mpegts_parse_sink_event (GstPad * pad, GstEvent * event); +static GstStateChangeReturn mpegts_parse_change_state (GstElement * element, + GstStateChange transition); +static gboolean mpegts_parse_src_pad_query (GstPad * pad, GstQuery * query); + +GST_BOILERPLATE (MpegTSParse, mpegts_parse, GstElement, GST_TYPE_ELEMENT); + +static const guint32 crc_tab[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* relicenced to LGPL from fluendo ts demuxer */ +static guint32 +mpegts_parse_calc_crc32 (guint8 * data, guint datalen) +{ + gint i; + guint32 crc = 0xffffffff; + + for (i = 0; i < datalen; i++) { + crc = (crc << 8) ^ crc_tab[((crc >> 24) ^ *data++) & 0xff]; + } + return crc; +} + +static void +mpegts_parse_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + 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_details (element_class, &mpegts_parse_details); +} + +static void +mpegts_parse_class_init (MpegTSParseClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_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; + element_class->change_state = mpegts_parse_change_state; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->set_property = mpegts_parse_set_property; + gobject_class->get_property = mpegts_parse_get_property; + gobject_class->dispose = mpegts_parse_dispose; + gobject_class->finalize = mpegts_parse_finalize; + + g_object_class_install_property (gobject_class, PROP_PROGRAM_NUMBERS, + g_param_spec_string ("program-numbers", + "Program Numbers", + "Colon separated list of programs", "", G_PARAM_READWRITE)); +} + +static gboolean +foreach_psi_pid_remove (gpointer key, gpointer value, gpointer data) +{ + return TRUE; +} + +static void +mpegts_parse_reset (MpegTSParse * parse) +{ + mpegts_packetizer_clear (parse->packetizer); + g_hash_table_foreach_remove (parse->psi_pids, foreach_psi_pid_remove, NULL); + + /* PAT */ + g_hash_table_insert (parse->psi_pids, + GINT_TO_POINTER (0), GINT_TO_POINTER (1)); + parse->pat = NULL; + /* pmt pids will be added and removed dynamically */ + +} + +static void +mpegts_parse_init (MpegTSParse * parse, MpegTSParseClass * klass) +{ + parse->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink"); + gst_pad_set_chain_function (parse->sinkpad, mpegts_parse_chain); + gst_pad_set_event_function (parse->sinkpad, mpegts_parse_sink_event); + gst_element_add_pad (GST_ELEMENT (parse), parse->sinkpad); + + parse->disposed = FALSE; + parse->packetizer = mpegts_packetizer_new (); + parse->program_numbers = g_strdup (""); + parse->pads_to_add = NULL; + parse->programs = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) mpegts_parse_free_program); + parse->psi_pids = g_hash_table_new (g_direct_hash, g_direct_equal); + mpegts_parse_reset (parse); +} + +static void +mpegts_parse_dispose (GObject * object) +{ + MpegTSParse *parse = GST_MPEGTS_PARSE (object); + + if (!parse->disposed) { + g_object_unref (parse->packetizer); + parse->disposed = TRUE; + } + + if (G_OBJECT_CLASS (parent_class)->dispose) + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +mpegts_parse_finalize (GObject * object) +{ + MpegTSParse *parse = GST_MPEGTS_PARSE (object); + + g_free (parse->program_numbers); + if (parse->pat) { + gst_structure_free (parse->pat); + parse->pat = NULL; + } + g_hash_table_destroy (parse->programs); + g_hash_table_destroy (parse->psi_pids); + + if (G_OBJECT_CLASS (parent_class)->finalize) + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +mpegts_parse_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + MpegTSParse *parse = GST_MPEGTS_PARSE (object); + + switch (prop_id) { + case PROP_PROGRAM_NUMBERS: + mpegts_parse_reset_selected_programs (parse, g_value_dup_string (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) +{ + MpegTSParse *parse = GST_MPEGTS_PARSE (object); + + switch (prop_id) { + case PROP_PROGRAM_NUMBERS: + g_value_set_string (value, parse->program_numbers); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static MpegTSParseProgram * +mpegts_parse_add_program (MpegTSParse * parse, + gint program_number, guint16 pmt_pid) +{ + MpegTSParseProgram *program; + + program = g_new0 (MpegTSParseProgram, 1); + program->program_number = program_number; + program->pmt_pid = pmt_pid; + program->pcr_pid = G_MAXUINT16; + program->streams = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) mpegts_parse_free_stream); + program->patcount = 1; + program->selected = 0; + program->active = FALSE; + + g_hash_table_insert (parse->programs, + GINT_TO_POINTER (program_number), program); + + return program; +} + +static MpegTSParseProgram * +mpegts_parse_get_program (MpegTSParse * parse, gint program_number) +{ + MpegTSParseProgram *program; + + program = (MpegTSParseProgram *) g_hash_table_lookup (parse->programs, + GINT_TO_POINTER ((gint) program_number)); + + return program; +} + +static GstPad * +mpegts_parse_activate_program (MpegTSParse * parse, + MpegTSParseProgram * program) +{ + MpegTSParsePad *tspad; + gchar *pad_name; + + pad_name = g_strdup_printf ("program_%d", program->program_number); + + tspad = mpegts_parse_create_tspad (parse, pad_name); + tspad->program_number = program->program_number; + tspad->program = program; + program->tspad = tspad; + g_free (pad_name); + gst_pad_set_active (tspad->pad, TRUE); + program->active = TRUE; + + return tspad->pad; +} + +static GstPad * +mpegts_parse_deactivate_program (MpegTSParse * parse, + MpegTSParseProgram * program) +{ + MpegTSParsePad *tspad; + + tspad = program->tspad; + gst_pad_set_active (tspad->pad, FALSE); + program->active = FALSE; + + /* tspad will be destroyed in GstElementClass::pad_removed */ + + return tspad->pad; +} + +static void +mpegts_parse_free_program (MpegTSParseProgram * program) +{ + if (program->pmt_info) + gst_structure_free (program->pmt_info); + + g_hash_table_destroy (program->streams); + + g_free (program); +} + +static void +mpegts_parse_remove_program (MpegTSParse * parse, gint program_number) +{ + g_hash_table_remove (parse->programs, GINT_TO_POINTER (program_number)); +} + +static void +mpegts_parse_sync_program_pads (MpegTSParse * parse, + GList * to_add, GList * to_remove) +{ + GList *walk; + + for (walk = to_remove; walk; walk = walk->next) + gst_element_remove_pad (GST_ELEMENT (parse), GST_PAD (walk->data)); + + for (walk = to_add; walk; walk = walk->next) + gst_element_add_pad (GST_ELEMENT (parse), GST_PAD (walk->data)); + + if (to_add) + g_list_free (to_add); + + if (to_remove) + g_list_free (to_remove); +} + + +static MpegTSParseStream * +mpegts_parse_program_add_stream (MpegTSParse * parse, + MpegTSParseProgram * program, guint16 pid, guint8 stream_type) +{ + MpegTSParseStream *stream; + + stream = g_new0 (MpegTSParseStream, 1); + stream->pid = pid; + stream->stream_type = stream_type; + + g_hash_table_insert (program->streams, GINT_TO_POINTER ((gint) pid), stream); + + return stream; +} + +static void +foreach_program_activate_or_deactivate (gpointer key, gpointer value, + gpointer data) +{ + MpegTSParse *parse = GST_MPEGTS_PARSE (data); + MpegTSParseProgram *program = (MpegTSParseProgram *) value; + + /* at this point selected programs have program->selected == 2, + * unselected programs thay may have to be deactivated have selected == 1 and + * unselected inactive programs have selected == 0 */ + + switch (--program->selected) { + case 1: + /* selected */ + if (!program->active && program->pmt_pid != G_MAXUINT16) + parse->pads_to_add = g_list_append (parse->pads_to_add, + mpegts_parse_activate_program (parse, program)); + break; + case 0: + /* unselected */ + if (program->active) + parse->pads_to_remove = g_list_append (parse->pads_to_remove, + mpegts_parse_deactivate_program (parse, program)); + break; + case -1: + /* was already unselected */ + program->selected = 0; + break; + default: + g_return_if_reached (); + } +} + +static void +mpegts_parse_reset_selected_programs (MpegTSParse * parse, + gchar * program_numbers) +{ + GList *pads_to_add = NULL; + GList *pads_to_remove = NULL; + + GST_OBJECT_LOCK (parse); + if (parse->program_numbers) + g_free (parse->program_numbers); + + parse->program_numbers = program_numbers; + + if (*parse->program_numbers != '\0') { + gint program_number; + MpegTSParseProgram *program; + gchar **progs, **walk; + + progs = g_strsplit (parse->program_numbers, ":", 0); + + walk = progs; + while (*walk != NULL) { + program_number = strtol (*walk, NULL, 0); + program = mpegts_parse_get_program (parse, program_number); + if (program == NULL) + /* create the program, it will get activated once we get a PMT for it */ + program = mpegts_parse_add_program (parse, program_number, G_MAXUINT16); + + program->selected = 2; + ++walk; + } + g_strfreev (progs); + } + + g_hash_table_foreach (parse->programs, + foreach_program_activate_or_deactivate, parse); + + pads_to_add = parse->pads_to_add; + parse->pads_to_add = NULL; + pads_to_remove = parse->pads_to_remove; + parse->pads_to_remove = NULL; + GST_OBJECT_UNLOCK (parse); + + mpegts_parse_sync_program_pads (parse, pads_to_add, pads_to_remove); +} + +static void +mpegts_parse_free_stream (MpegTSParseStream * stream) +{ + g_free (stream); +} + +static void +mpegts_parse_program_remove_stream (MpegTSParse * parse, + MpegTSParseProgram * program, guint16 pid) +{ + g_hash_table_remove (program->streams, GINT_TO_POINTER ((gint) pid)); +} + +static MpegTSParsePad * +mpegts_parse_create_tspad (MpegTSParse * 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 (MpegTSParse * parse, MpegTSParsePad * tspad) +{ + /* free the wrapper */ + g_free (tspad); +} + +static void +mpegts_parse_pad_removed (GstElement * element, GstPad * pad) +{ + MpegTSParsePad *tspad; + MpegTSParse *parse = GST_MPEGTS_PARSE (element); + + if (gst_pad_get_direction (pad) == GST_PAD_SINK) + return; + + tspad = (MpegTSParsePad *) gst_pad_get_element_private (pad); + mpegts_parse_destroy_tspad (parse, tspad); + + 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 * unused) +{ + MpegTSParse *parse; + gchar *name; + GstPad *pad; + + g_return_val_if_fail (template != NULL, NULL); + g_return_val_if_fail (GST_IS_MPEGTS_PARSE (element), NULL); + + parse = GST_MPEGTS_PARSE (element); + + GST_OBJECT_LOCK (element); + name = g_strdup_printf ("src%d", parse->req_pads++); + GST_OBJECT_UNLOCK (element); + + pad = mpegts_parse_create_tspad (parse, name)->pad; + gst_pad_set_active (pad, TRUE); + gst_element_add_pad (element, pad); + + return pad; +} + +static void +mpegts_parse_release_pad (GstElement * element, GstPad * pad) +{ + g_return_if_fail (GST_IS_MPEGTS_PARSE (element)); + + 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 (MpegTSParse * parse, MpegTSParsePad * tspad, + MpegTSPacketizerSection * section, GstBuffer * buffer) +{ + GstFlowReturn ret = GST_FLOW_NOT_LINKED; + 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; + ret = GST_FLOW_OK; + } + } + GST_DEBUG_OBJECT (parse, + "pushing section: %d program number: %d table_id: %d", to_push, + tspad->program_number, section->table_id); + if (to_push) { + ret = gst_pad_push (tspad->pad, buffer); + } else { + gst_buffer_unref (buffer); + if (gst_pad_is_linked (tspad->pad)) + ret = GST_FLOW_OK; + } + + return ret; +} + +static GstFlowReturn +mpegts_parse_tspad_push (MpegTSParse * parse, MpegTSParsePad * tspad, + guint16 pid, GstBuffer * buffer) +{ + GstFlowReturn ret = GST_FLOW_NOT_LINKED; + GHashTable *pad_pids = NULL; + + if (tspad->program_number != -1) { + if (tspad->program) { + pad_pids = tspad->program->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 */ + gst_buffer_unref (buffer); + ret = GST_FLOW_OK; + goto out; + } + } + + if (pad_pids == NULL || + g_hash_table_lookup (pad_pids, GINT_TO_POINTER ((gint) pid)) != NULL) { + /* push if there's no filter or if the pid is in the filter */ + ret = gst_pad_push (tspad->pad, buffer); + } else { + gst_buffer_unref (buffer); + if (gst_pad_is_linked (tspad->pad)) + ret = GST_FLOW_OK; + } + +out: + return ret; +} + +static void +pad_clear_for_push (GstPad * pad, MpegTSParse * 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 (MpegTSParse * parse, MpegTSPacketizerPacket * packet, + MpegTSPacketizerSection * section) +{ + GstIterator *iterator; + gboolean done = FALSE; + gpointer pad = NULL; + MpegTSParsePad *tspad; + guint16 pid; + GstBuffer *buffer; + GstFlowReturn ret; + GstCaps *caps; + + pid = packet->pid; + buffer = packet->buffer; + /* we have the same caps on all the src pads */ + caps = gst_static_pad_template_get_caps (&src_template); + gst_buffer_set_caps (buffer, caps); + gst_caps_unref (caps); + + GST_OBJECT_LOCK (parse); + /* clear tspad->pushed on pads */ + g_list_foreach (GST_ELEMENT_CAST (parse)->srcpads, + (GFunc) pad_clear_for_push, parse); + if (GST_ELEMENT_CAST (parse)->srcpads) + ret = GST_FLOW_NOT_LINKED; + else + ret = GST_FLOW_OK; + GST_OBJECT_UNLOCK (parse); + + iterator = gst_element_iterate_src_pads (GST_ELEMENT_CAST (parse)); + while (!done) { + switch (gst_iterator_next (iterator, &pad)) { + case GST_ITERATOR_OK: + tspad = gst_pad_get_element_private (GST_PAD (pad)); + + /* make sure to push only once if the iterator resyncs */ + if (!tspad->pushed) { + /* ref the buffer as gst_pad_push takes a ref but we want to reuse the + * same buffer for next pushes */ + gst_buffer_ref (buffer); + if (section) { + tspad->flow_return = + mpegts_parse_tspad_push_section (parse, tspad, section, buffer); + } else { + tspad->flow_return = + mpegts_parse_tspad_push (parse, tspad, pid, buffer); + } + tspad->pushed = TRUE; + + if (GST_FLOW_IS_FATAL (tspad->flow_return)) { + /* return the error upstream */ + ret = tspad->flow_return; + done = TRUE; + } + } + + if (ret == GST_FLOW_NOT_LINKED) + ret = tspad->flow_return; + + /* the iterator refs the pad */ + g_object_unref (GST_PAD (pad)); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iterator); + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + default: + g_warning ("this should not be reached"); + } + } + + gst_iterator_free (iterator); + + gst_buffer_unref (buffer); + packet->buffer = NULL; + + return ret; +} + +static gboolean +mpegts_parse_is_psi (MpegTSParse * parse, MpegTSPacketizerPacket * packet) +{ + gboolean retval = FALSE; + guint8 table_id; + int i; + guint8 si_tables[] = { 0x00, 0x01, 0x02, 0x03, 0x40, 0x41, 0x42, 0x46, 0x4A, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, + 0x72, 0x73, 0x7E, 0x7F, TABLE_ID_UNSET + }; + if (g_hash_table_lookup (parse->psi_pids, + GINT_TO_POINTER ((gint) packet->pid)) != NULL) + retval = TRUE; + if (!retval) { + if (packet->payload_unit_start_indicator) { + table_id = *(packet->data); + i = 0; + while (si_tables[i] != TABLE_ID_UNSET) { + if (si_tables[i] == table_id) { + GST_DEBUG_OBJECT (parse, "Packet has table id 0x%x", table_id); + retval = TRUE; + break; + } + i++; + } + } else { + MpegTSPacketizerStream *stream = (MpegTSPacketizerStream *) + g_hash_table_lookup (parse->packetizer->streams, + GINT_TO_POINTER ((gint) packet->pid)); + + if (stream) { + i = 0; + GST_DEBUG_OBJECT (parse, "section table id: 0x%x", + stream->section_table_id); + while (si_tables[i] != TABLE_ID_UNSET) { + if (si_tables[i] == stream->section_table_id) { + retval = TRUE; + break; + } + i++; + } + } + } + } + GST_DEBUG_OBJECT (parse, "Packet of pid 0x%x is psi: %d", packet->pid, + retval); + return retval; +} + +static void +mpegts_parse_apply_pat (MpegTSParse * parse, GstStructure * pat_info) +{ + const GValue *value; + GstStructure *old_pat; + GstStructure *program_info; + guint program_number; + guint pid; + MpegTSParseProgram *program; + gint i; + GList *pads_to_add = NULL; + GList *pads_to_remove = NULL; + const GValue *programs; + gchar *dbg; + + old_pat = parse->pat; + parse->pat = gst_structure_copy (pat_info); + + dbg = gst_structure_to_string (pat_info); + GST_INFO_OBJECT (parse, "PAT %s", dbg); + g_free (dbg); + + gst_element_post_message (GST_ELEMENT_CAST (parse), + gst_message_new_element (GST_OBJECT (parse), + gst_structure_copy (pat_info))); + + GST_OBJECT_LOCK (parse); + programs = gst_structure_get_value (pat_info, "programs"); + /* activate the new table */ + for (i = 0; i < gst_value_list_get_size (programs); ++i) { + value = gst_value_list_get_value (programs, i); + + program_info = g_value_get_boxed (value); + gst_structure_get_uint (program_info, "program-number", &program_number); + gst_structure_get_uint (program_info, "pid", &pid); + + program = mpegts_parse_get_program (parse, program_number); + if (program) { + if (program->pmt_pid != pid) { + if (program->pmt_pid != G_MAXUINT16) { + /* pmt pid changed */ + g_hash_table_remove (parse->psi_pids, + GINT_TO_POINTER ((gint) program->pmt_pid)); + } + + program->pmt_pid = pid; + g_hash_table_insert (parse->psi_pids, + GINT_TO_POINTER ((gint) pid), GINT_TO_POINTER (1)); + } + + program->patcount += 1; + } else { + g_hash_table_insert (parse->psi_pids, + GINT_TO_POINTER ((gint) pid), GINT_TO_POINTER (1)); + program = mpegts_parse_add_program (parse, program_number, pid); + } + + if (program->selected && !program->active) + parse->pads_to_add = g_list_append (parse->pads_to_add, + mpegts_parse_activate_program (parse, program)); + } + + if (old_pat) { + /* deactivate the old table */ + + programs = gst_structure_get_value (old_pat, "programs"); + for (i = 0; i < gst_value_list_get_size (programs); ++i) { + value = gst_value_list_get_value (programs, i); + + program_info = g_value_get_boxed (value); + gst_structure_get_uint (program_info, "program-number", &program_number); + gst_structure_get_uint (program_info, "pid", &pid); + + program = mpegts_parse_get_program (parse, program_number); + if (program == NULL) { + GST_DEBUG_OBJECT (parse, "broken PAT, duplicated entry for program %d", + program_number); + continue; + } + + if (--program->patcount > 0) + /* the program has been referenced by the new pat, keep it */ + continue; + + { + gchar *dbg = gst_structure_to_string (program_info); + + GST_INFO_OBJECT (parse, "PAT removing program %s", dbg); + g_free (dbg); + } + + if (program->active) + parse->pads_to_remove = g_list_append (parse->pads_to_remove, + mpegts_parse_deactivate_program (parse, program)); + + mpegts_parse_remove_program (parse, program_number); + g_hash_table_remove (parse->psi_pids, GINT_TO_POINTER ((gint) pid)); + } + + gst_structure_free (old_pat); + } + + pads_to_add = parse->pads_to_add; + parse->pads_to_add = NULL; + pads_to_remove = parse->pads_to_remove; + parse->pads_to_remove = NULL; + GST_OBJECT_UNLOCK (parse); + + mpegts_parse_sync_program_pads (parse, pads_to_add, pads_to_remove); +} + +static void +mpegts_parse_apply_pmt (MpegTSParse * parse, + guint16 pmt_pid, GstStructure * pmt_info) +{ + MpegTSParseProgram *program; + guint program_number; + guint pcr_pid; + guint pid; + guint stream_type; + GstStructure *stream; + gint i; + const GValue *old_streams; + const GValue *new_streams; + const GValue *value; + + gst_structure_get_uint (pmt_info, "program-number", &program_number); + gst_structure_get_uint (pmt_info, "pcr-pid", &pcr_pid); + new_streams = gst_structure_get_value (pmt_info, "streams"); + + GST_OBJECT_LOCK (parse); + program = mpegts_parse_get_program (parse, program_number); + if (program) { + if (program->pmt_info) { + /* deactivate old pmt */ + old_streams = gst_structure_get_value (program->pmt_info, "streams"); + + for (i = 0; i < gst_value_list_get_size (old_streams); ++i) { + value = gst_value_list_get_value (old_streams, i); + stream = g_value_get_boxed (value); + gst_structure_get_uint (stream, "pid", &pid); + gst_structure_get_uint (stream, "stream-type", &stream_type); + mpegts_parse_program_remove_stream (parse, program, (guint16) pid); + } + + /* remove pcr stream */ + mpegts_parse_program_remove_stream (parse, program, program->pcr_pid); + + gst_structure_free (program->pmt_info); + program->pmt_info = NULL; + } + } else { + /* no PAT?? */ + g_hash_table_insert (parse->psi_pids, + GINT_TO_POINTER ((gint) pmt_pid), GINT_TO_POINTER (1)); + program = mpegts_parse_add_program (parse, program_number, pid); + } + + /* activate new pmt */ + program->pmt_info = gst_structure_copy (pmt_info); + program->pmt_pid = pmt_pid; + program->pcr_pid = pcr_pid; + mpegts_parse_program_add_stream (parse, program, (guint16) pcr_pid, -1); + + for (i = 0; i < gst_value_list_get_size (new_streams); ++i) { + value = gst_value_list_get_value (new_streams, i); + stream = g_value_get_boxed (value); + + gst_structure_get_uint (stream, "pid", &pid); + gst_structure_get_uint (stream, "stream-type", &stream_type); + mpegts_parse_program_add_stream (parse, program, + (guint16) pid, (guint8) stream_type); + } + GST_OBJECT_UNLOCK (parse); + + { + gchar *dbg = gst_structure_to_string (pmt_info); + + GST_DEBUG_OBJECT (parse, "new pmt %s", dbg); + g_free (dbg); + } + + gst_element_post_message (GST_ELEMENT_CAST (parse), + gst_message_new_element (GST_OBJECT (parse), + gst_structure_copy (pmt_info))); +} + +static void +mpegts_parse_apply_nit (MpegTSParse * parse, + guint16 pmt_pid, GstStructure * nit_info) +{ + gst_element_post_message (GST_ELEMENT_CAST (parse), + gst_message_new_element (GST_OBJECT (parse), + gst_structure_copy (nit_info))); +} + +static void +mpegts_parse_apply_sdt (MpegTSParse * parse, + guint16 pmt_pid, GstStructure * sdt_info) +{ + gst_element_post_message (GST_ELEMENT_CAST (parse), + gst_message_new_element (GST_OBJECT (parse), + gst_structure_copy (sdt_info))); +} + +static void +mpegts_parse_apply_eit (MpegTSParse * parse, + guint16 pmt_pid, GstStructure * eit_info) +{ + gst_element_post_message (GST_ELEMENT_CAST (parse), + gst_message_new_element (GST_OBJECT (parse), + gst_structure_copy (eit_info))); +} + +static gboolean +mpegts_parse_handle_psi (MpegTSParse * parse, MpegTSPacketizerSection * section) +{ + gboolean res = TRUE; + GstStructure *structure = NULL; + + if (mpegts_parse_calc_crc32 (GST_BUFFER_DATA (section->buffer), + GST_BUFFER_SIZE (section->buffer)) != 0) { + GST_WARNING_OBJECT (parse, "bad crc in psi pid 0x%x", section->pid); + return FALSE; + } + + switch (section->table_id) { + case 0x00: + /* PAT */ + structure = mpegts_packetizer_parse_pat (parse->packetizer, section); + if (structure) + mpegts_parse_apply_pat (parse, structure); + else + res = FALSE; + + break; + case 0x02: + structure = mpegts_packetizer_parse_pmt (parse->packetizer, section); + if (structure) + mpegts_parse_apply_pmt (parse, section->pid, structure); + else + res = FALSE; + + break; + case 0x40: + /* NIT, actual network */ + case 0x41: + /* NIT, other network */ + structure = mpegts_packetizer_parse_nit (parse->packetizer, section); + if (structure) + mpegts_parse_apply_nit (parse, section->pid, structure); + else + res = FALSE; + + break; + case 0x42: + case 0x46: + structure = mpegts_packetizer_parse_sdt (parse->packetizer, section); + if (structure) + mpegts_parse_apply_sdt (parse, section->pid, structure); + else + res = FALSE; + break; + case 0x4E: + case 0x4F: + /* EIT, present/following */ + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + /* EIT, schedule */ + structure = mpegts_packetizer_parse_eit (parse->packetizer, section); + if (structure) + mpegts_parse_apply_eit (parse, section->pid, structure); + else + res = FALSE; + break; + default: + break; + } + + if (structure) + gst_structure_free (structure); + + return res; +} + +static gboolean +mpegts_parse_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean res; + MpegTSParse *parse = + GST_MPEGTS_PARSE (gst_object_get_parent (GST_OBJECT (pad))); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + mpegts_packetizer_clear (parse->packetizer); + res = gst_pad_event_default (pad, event); + break; + default: + res = gst_pad_event_default (pad, event); + } + + gst_object_unref (parse); + return res; +} + +static GstFlowReturn +mpegts_parse_chain (GstPad * pad, GstBuffer * buf) +{ + GstFlowReturn res = GST_FLOW_OK; + MpegTSParse *parse; + gboolean parsed; + MpegTSPacketizer *packetizer; + MpegTSPacketizerPacket packet; + + parse = GST_MPEGTS_PARSE (gst_object_get_parent (GST_OBJECT (pad))); + packetizer = parse->packetizer; + + mpegts_packetizer_push (parse->packetizer, buf); + while (mpegts_packetizer_has_packets (parse->packetizer) && + !GST_FLOW_IS_FATAL (res)) { + /* get the next packet */ + parsed = mpegts_packetizer_next_packet (packetizer, &packet); + if (!parsed) + /* bad header, skip the packet */ + goto next; + + /* parse PSI data */ + if (packet.payload != NULL && mpegts_parse_is_psi (parse, &packet)) { + MpegTSPacketizerSection section; + + parsed = mpegts_packetizer_push_section (packetizer, &packet, §ion); + if (!parsed) + /* bad section data */ + goto next; + + if (section.complete) { + /* section complete */ + parsed = mpegts_parse_handle_psi (parse, §ion); + gst_buffer_unref (section.buffer); + + if (!parsed) + /* bad PSI table */ + goto next; + } + /* we need to push section packet downstream */ + res = mpegts_parse_push (parse, &packet, §ion); + + } else { + /* push the packet downstream */ + res = mpegts_parse_push (parse, &packet, NULL); + } + + next: + mpegts_packetizer_clear_packet (parse->packetizer, &packet); + } + + gst_object_unref (parse); + return res; +} + +static GstStateChangeReturn +mpegts_parse_change_state (GstElement * element, GstStateChange transition) +{ + MpegTSParse *parse; + GstStateChangeReturn ret; + + parse = GST_MPEGTS_PARSE (element); + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + mpegts_parse_reset (parse); + break; + default: + break; + } + + return ret; +} + +static gboolean +mpegts_parse_src_pad_query (GstPad * pad, GstQuery * query) +{ + MpegTSParse *parse = GST_MPEGTS_PARSE (gst_pad_get_parent (pad)); + gboolean res; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + if ((res = gst_pad_peer_query (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, query); + } + + return res; +} + +gboolean +gst_mpegtsparse_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (mpegts_parse_debug, "mpegtsparse", 0, + "MPEG transport stream parser"); + + mpegts_packetizer_init_debug (); + gst_mpegtsdesc_init_debug (); + + return gst_element_register (plugin, "mpegtsparse", + GST_RANK_NONE, GST_TYPE_MPEGTS_PARSE); +} diff --git a/gst/mpegdemux/mpegtsparse.h b/gst/mpegdemux/mpegtsparse.h new file mode 100644 index 00000000..34a78634 --- /dev/null +++ b/gst/mpegdemux/mpegtsparse.h @@ -0,0 +1,83 @@ +/* + * mpegts_parse.h - GStreamer MPEG transport stream parser + * Copyright (C) 2007 Alessandro Decina + * + * Authors: + * Alessandro Decina + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef GST_MPEG_TS_PARSE_H +#define GST_MPEG_TS_PARSE_H + +#include +#include "mpegtspacketizer.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MPEGTS_PARSE \ + (mpegts_parse_get_type()) +#define GST_MPEGTS_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MPEGTS_PARSE,MpegTSParse)) +#define GST_MPEGTS_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MPEGTS_PARSE,MpegTSParseClass)) +#define GST_IS_MPEGTS_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MPEGTS_PARSE)) +#define GST_IS_MPEGTS_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MPEGTS_PARSE)) + +typedef struct _MpegTSParse MpegTSParse; +typedef struct _MpegTSParseClass MpegTSParseClass; + +struct _MpegTSParse { + GstElement element; + + GstPad *sinkpad; + + /* the following vars must be protected with the OBJECT_LOCK as they can be + * accessed from the application thread and the streaming thread */ + gchar *program_numbers; + GList *pads_to_add; + GList *pads_to_remove; + GHashTable *programs; + guint req_pads; + + GstStructure *pat; + MpegTSPacketizer *packetizer; + GHashTable *psi_pids; + gboolean disposed; +}; + +struct _MpegTSParseClass { + GstElementClass parent_class; + + /* signals */ + void (*pat_info) (GstStructure *pat); + void (*pmt_info) (GstStructure *pmt); + void (*nit_info) (GstStructure *nit); + void (*sdt_info) (GstStructure *sdt); + void (*eit_info) (GstStructure *eit); +}; + +GType gst_mpegts_parse_get_type(void); + +gboolean gst_mpegtsparse_plugin_init (GstPlugin * plugin); + +G_END_DECLS + +#endif /* GST_MPEG_TS_PARSE_H */ -- cgit v1.2.1