/* * 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., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include /* FIXME 0.11: suppress warnings for deprecated API such as GValueArray * with newer GLib versions (>= 2.31.0) */ #define GLIB_DISABLE_DEPRECATION_WARNINGS /* Skew calculation pameters */ #define MAX_TIME (2 * GST_SECOND) /* maximal PCR time */ #define PCR_MAX_VALUE (((((guint64)1)<<33) * 300) + 298) #define PCR_GST_MAX_VALUE (PCR_MAX_VALUE * GST_MSECOND / (27000)) #define PTS_DTS_MAX_VALUE (((guint64)1) << 33) #include "mpegtspacketizer.h" #include "gstmpegdesc.h" GST_DEBUG_CATEGORY_STATIC (mpegts_packetizer_debug); #define GST_CAT_DEFAULT mpegts_packetizer_debug static GQuark QUARK_PAT; static GQuark QUARK_TRANSPORT_STREAM_ID; static GQuark QUARK_PROGRAM_NUMBER; static GQuark QUARK_PID; static GQuark QUARK_PROGRAMS; static GQuark QUARK_CAT; static GQuark QUARK_PMT; static GQuark QUARK_PCR_PID; static GQuark QUARK_VERSION_NUMBER; static GQuark QUARK_DESCRIPTORS; static GQuark QUARK_STREAM_TYPE; static GQuark QUARK_STREAMS; static GQuark QUARK_NIT; static GQuark QUARK_NETWORK_ID; static GQuark QUARK_CURRENT_NEXT_INDICATOR; static GQuark QUARK_ACTUAL_NETWORK; static GQuark QUARK_NETWORK_NAME; static GQuark QUARK_ORIGINAL_NETWORK_ID; static GQuark QUARK_TRANSPORTS; static GQuark QUARK_TERRESTRIAL; static GQuark QUARK_CABLE; static GQuark QUARK_FREQUENCY; static GQuark QUARK_MODULATION; static GQuark QUARK_BANDWIDTH; static GQuark QUARK_CONSTELLATION; static GQuark QUARK_HIERARCHY; static GQuark QUARK_CODE_RATE_HP; static GQuark QUARK_CODE_RATE_LP; static GQuark QUARK_GUARD_INTERVAL; static GQuark QUARK_TRANSMISSION_MODE; static GQuark QUARK_OTHER_FREQUENCY; static GQuark QUARK_SYMBOL_RATE; static GQuark QUARK_INNER_FEC; static GQuark QUARK_DELIVERY; static GQuark QUARK_CHANNELS; static GQuark QUARK_LOGICAL_CHANNEL_NUMBER; static GQuark QUARK_SDT; static GQuark QUARK_ACTUAL_TRANSPORT_STREAM; static GQuark QUARK_SERVICES; static GQuark QUARK_EIT; static GQuark QUARK_SERVICE_ID; static GQuark QUARK_PRESENT_FOLLOWING; static GQuark QUARK_SEGMENT_LAST_SECTION_NUMBER; static GQuark QUARK_LAST_TABLE_ID; static GQuark QUARK_EVENTS; static GQuark QUARK_NAME; static GQuark QUARK_DESCRIPTION; static GQuark QUARK_EXTENDED_ITEM; static GQuark QUARK_EXTENDED_ITEMS; static GQuark QUARK_TEXT; static GQuark QUARK_EXTENDED_TEXT; static GQuark QUARK_EVENT_ID; static GQuark QUARK_YEAR; static GQuark QUARK_MONTH; static GQuark QUARK_DAY; static GQuark QUARK_HOUR; static GQuark QUARK_MINUTE; static GQuark QUARK_SECOND; static GQuark QUARK_DURATION; static GQuark QUARK_RUNNING_STATUS; static GQuark QUARK_FREE_CA_MODE; #define MAX_KNOWN_ICONV 25 /* All these conversions will be to UTF8 */ typedef enum { _ICONV_UNKNOWN = -1, _ICONV_ISO8859_1, _ICONV_ISO8859_2, _ICONV_ISO8859_3, _ICONV_ISO8859_4, _ICONV_ISO8859_5, _ICONV_ISO8859_6, _ICONV_ISO8859_7, _ICONV_ISO8859_8, _ICONV_ISO8859_9, _ICONV_ISO8859_10, _ICONV_ISO8859_11, _ICONV_ISO8859_12, _ICONV_ISO8859_13, _ICONV_ISO8859_14, _ICONV_ISO8859_15, _ICONV_ISO10646_UC2, _ICONV_EUC_KR, _ICONV_GB2312, _ICONV_UTF_16BE, _ICONV_ISO10646_UTF8, _ICONV_ISO6937, /* Insert more here if needed */ _ICONV_MAX } LocalIconvCode; static const gchar *iconvtablename[] = { "iso-8859-1", "iso-8859-2", "iso-8859-3", "iso-8859-4", "iso-8859-5", "iso-8859-6", "iso-8859-7", "iso-8859-8", "iso-8859-9", "iso-8859-10", "iso-8859-11", "iso-8859-12", "iso-8859-13", "iso-8859-14", "iso-8859-15", "ISO-10646/UCS2", "EUC-KR", "GB2312", "UTF-16BE", "ISO-10646/UTF8", "iso6937" /* Insert more here if needed */ }; #define MPEGTS_PACKETIZER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_MPEGTS_PACKETIZER, MpegTSPacketizerPrivate)) static void _init_local (void); G_DEFINE_TYPE_EXTENDED (MpegTSPacketizer2, mpegts_packetizer, G_TYPE_OBJECT, 0, _init_local ()); /* Maximum number of MpegTSPcr * 256 should be sufficient for most multiplexes */ #define MAX_PCR_OBS_CHANNELS 256 typedef struct _MpegTSPCR { guint16 pid; /* Following variables are only active/used when * calculate_skew is TRUE */ GstClockTime base_time; GstClockTime base_pcrtime; GstClockTime prev_out_time; GstClockTime prev_in_time; GstClockTime last_pcrtime; gint64 window[MAX_WINDOW]; guint window_pos; guint window_size; gboolean window_filling; gint64 window_min; gint64 skew; gint64 prev_send_diff; /* Offset to apply to PCR to handle wraparounds */ guint64 pcroffset; /* Used for bitrate calculation */ /* FIXME : Replace this later on with a balanced tree or sequence */ guint64 first_offset; guint64 first_pcr; GstClockTime first_pcr_ts; guint64 last_offset; guint64 last_pcr; GstClockTime last_pcr_ts; } MpegTSPCR; struct _MpegTSPacketizerPrivate { /* Shortcuts for adapter usage */ guint available; guint8 *mapped; guint offset; guint mapped_size; /* Reference offset */ guint64 refoffset; guint nb_seen_offsets; /* Last inputted timestamp */ GstClockTime last_in_time; /* offset to observations table */ guint8 pcrtablelut[0x2000]; MpegTSPCR *observations[MAX_PCR_OBS_CHANNELS]; guint8 lastobsid; /* Conversion tables */ GIConv iconvs[_ICONV_MAX]; }; static void mpegts_packetizer_dispose (GObject * object); static void mpegts_packetizer_finalize (GObject * object); static gchar *get_encoding_and_convert (MpegTSPacketizer2 * packetizer, const gchar * text, guint length); static GstClockTime calculate_skew (MpegTSPCR * pcr, guint64 pcrtime, GstClockTime time); static void record_pcr (MpegTSPacketizer2 * packetizer, MpegTSPCR * pcrtable, guint64 pcr, guint64 offset); #define CONTINUITY_UNSET 255 #define MAX_CONTINUITY 15 #define VERSION_NUMBER_UNSET 255 #define TABLE_ID_UNSET 0xFF #define PACKET_SYNC_BYTE 0x47 static MpegTSPCR * get_pcr_table (MpegTSPacketizer2 * packetizer, guint16 pid) { MpegTSPacketizerPrivate *priv = packetizer->priv; MpegTSPCR *res; res = priv->observations[priv->pcrtablelut[pid]]; if (G_UNLIKELY (res == NULL)) { /* If we don't have a PCR table for the requested PID, create one .. */ res = g_new0 (MpegTSPCR, 1); /* Add it to the last table position */ priv->observations[priv->lastobsid] = res; /* Update the pcrtablelut */ priv->pcrtablelut[pid] = priv->lastobsid; /* And increment the last know slot */ priv->lastobsid++; /* Finally set the default values */ res->pid = pid; res->first_offset = -1; res->first_pcr = -1; res->first_pcr_ts = GST_CLOCK_TIME_NONE; res->last_offset = -1; res->last_pcr = -1; res->last_pcr_ts = GST_CLOCK_TIME_NONE; res->base_time = GST_CLOCK_TIME_NONE; res->base_pcrtime = GST_CLOCK_TIME_NONE; res->last_pcrtime = GST_CLOCK_TIME_NONE; res->window_pos = 0; res->window_filling = TRUE; res->window_min = 0; res->skew = 0; res->prev_send_diff = GST_CLOCK_TIME_NONE; res->prev_out_time = GST_CLOCK_TIME_NONE; res->pcroffset = 0; } return res; } static void flush_observations (MpegTSPacketizer2 * packetizer) { MpegTSPacketizerPrivate *priv = packetizer->priv; gint i; for (i = 0; i < priv->lastobsid; i++) { g_free (priv->observations[i]); priv->observations[i] = NULL; } memset (priv->pcrtablelut, 0xff, 0x200); priv->lastobsid = 0; } 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; subtable->crc = 0; return subtable; } static MpegTSPacketizerStream * mpegts_packetizer_stream_new (void) { MpegTSPacketizerStream *stream; stream = (MpegTSPacketizerStream *) g_new0 (MpegTSPacketizerStream, 1); stream->continuity_counter = CONTINUITY_UNSET; stream->subtables = NULL; stream->section_table_id = TABLE_ID_UNSET; return stream; } static void mpegts_packetizer_clear_section (MpegTSPacketizerStream * stream) { stream->continuity_counter = CONTINUITY_UNSET; stream->section_length = 0; stream->section_offset = 0; stream->section_table_id = TABLE_ID_UNSET; } static void mpegts_packetizer_stream_free (MpegTSPacketizerStream * stream) { mpegts_packetizer_clear_section (stream); if (stream->section_data) g_free (stream->section_data); g_slist_foreach (stream->subtables, (GFunc) g_free, NULL); g_slist_free (stream->subtables); g_free (stream); } static void mpegts_packetizer_class_init (MpegTSPacketizer2Class * klass) { GObjectClass *gobject_class; g_type_class_add_private (klass, sizeof (MpegTSPacketizerPrivate)); gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = mpegts_packetizer_dispose; gobject_class->finalize = mpegts_packetizer_finalize; } static void mpegts_packetizer_init (MpegTSPacketizer2 * packetizer) { MpegTSPacketizerPrivate *priv; guint i; priv = packetizer->priv = MPEGTS_PACKETIZER_GET_PRIVATE (packetizer); packetizer->adapter = gst_adapter_new (); packetizer->offset = 0; packetizer->empty = TRUE; packetizer->streams = g_new0 (MpegTSPacketizerStream *, 8192); packetizer->know_packet_size = FALSE; packetizer->calculate_skew = FALSE; packetizer->calculate_offset = FALSE; priv->available = 0; priv->mapped = NULL; priv->mapped_size = 0; priv->offset = 0; memset (priv->pcrtablelut, 0xff, 0x200); memset (priv->observations, 0x0, sizeof (priv->observations)); for (i = 0; i < _ICONV_MAX; i++) priv->iconvs[i] = (GIConv) - 1; priv->lastobsid = 0; priv->nb_seen_offsets = 0; priv->refoffset = -1; priv->last_in_time = GST_CLOCK_TIME_NONE; } static void mpegts_packetizer_dispose (GObject * object) { MpegTSPacketizer2 *packetizer = GST_MPEGTS_PACKETIZER (object); guint i; if (!packetizer->disposed) { if (packetizer->know_packet_size && packetizer->caps != NULL) { gst_caps_unref (packetizer->caps); packetizer->caps = NULL; packetizer->know_packet_size = FALSE; } if (packetizer->streams) { int i; for (i = 0; i < 8192; i++) { if (packetizer->streams[i]) mpegts_packetizer_stream_free (packetizer->streams[i]); } g_free (packetizer->streams); } gst_adapter_clear (packetizer->adapter); g_object_unref (packetizer->adapter); packetizer->disposed = TRUE; packetizer->offset = 0; packetizer->empty = TRUE; for (i = 0; i < _ICONV_MAX; i++) if (packetizer->priv->iconvs[i] != (GIConv) - 1) g_iconv_close (packetizer->priv->iconvs[i]); flush_observations (packetizer); } if (G_OBJECT_CLASS (mpegts_packetizer_parent_class)->dispose) G_OBJECT_CLASS (mpegts_packetizer_parent_class)->dispose (object); } static void mpegts_packetizer_finalize (GObject * object) { if (G_OBJECT_CLASS (mpegts_packetizer_parent_class)->finalize) G_OBJECT_CLASS (mpegts_packetizer_parent_class)->finalize (object); } static inline guint64 mpegts_packetizer_compute_pcr (const guint8 * data) { guint32 pcr1; guint16 pcr2; guint64 pcr, pcr_ext; pcr1 = GST_READ_UINT32_BE (data); pcr2 = GST_READ_UINT16_BE (data + 4); pcr = ((guint64) pcr1) << 1; pcr |= (pcr2 & 0x8000) >> 15; pcr_ext = (pcr2 & 0x01ff); return pcr * 300 + pcr_ext % 300; } static gboolean mpegts_packetizer_parse_adaptation_field_control (MpegTSPacketizer2 * packetizer, MpegTSPacketizerPacket * packet) { guint8 length, afcflags; guint8 *data; length = *packet->data++; /* an adaptation field with length 0 is valid and * can be used to insert a single stuffing byte */ if (!length) { packet->afc_flags = 0; return TRUE; } 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); } if (packet->data + length > packet->data_end) { GST_DEBUG ("PID %d afc length %d overflows the buffer current %d max %d", packet->pid, length, (gint) (packet->data - packet->data_start), (gint) (packet->data_end - packet->data_start)); return FALSE; } data = packet->data; packet->data += length; afcflags = packet->afc_flags = *data++; /* PCR */ if (afcflags & MPEGTS_AFC_PCR_FLAG) { MpegTSPCR *pcrtable = NULL; packet->pcr = mpegts_packetizer_compute_pcr (data); data += 6; GST_DEBUG ("pcr 0x%04x %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT ") offset:%" G_GUINT64_FORMAT, packet->pid, packet->pcr, GST_TIME_ARGS (PCRTIME_TO_GSTTIME (packet->pcr)), packet->offset); if (GST_CLOCK_TIME_IS_VALID (packet->origts) && packetizer->calculate_skew) { pcrtable = get_pcr_table (packetizer, packet->pid); packet->origts = calculate_skew (pcrtable, packet->pcr, packet->origts); } if (packetizer->calculate_offset) { if (!pcrtable) pcrtable = get_pcr_table (packetizer, packet->pid); record_pcr (packetizer, pcrtable, packet->pcr, packet->offset); } } /* OPCR */ if (afcflags & MPEGTS_AFC_OPCR_FLAG) { packet->opcr = mpegts_packetizer_compute_pcr (data); /* *data += 6; */ GST_DEBUG ("opcr %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT ")", packet->pcr, GST_TIME_ARGS (PCRTIME_TO_GSTTIME (packet->pcr))); } return TRUE; } static MpegTSPacketizerPacketReturn mpegts_packetizer_parse_packet (MpegTSPacketizer2 * packetizer, MpegTSPacketizerPacket * packet) { guint8 *data; data = packet->data_start; data++; /* transport_error_indicator 1 */ if (G_UNLIKELY (*data >> 7)) return PACKET_BAD; /* payload_unit_start_indicator 1 */ packet->payload_unit_start_indicator = (*data >> 6) & 0x01; /* transport_priority 1 */ /* PID 13 */ packet->pid = GST_READ_UINT16_BE (data) & 0x1FFF; data += 2; /* transport_scrambling_control 2 */ if (G_UNLIKELY (*data >> 6)) return PACKET_BAD; /* adaptation_field_control 2 */ packet->adaptation_field_control = (*data >> 4) & 0x03; /* continuity_counter 4 */ 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 PACKET_OK; } static gboolean mpegts_packetizer_parse_section_header (MpegTSPacketizer2 * packetizer, MpegTSPacketizerStream * stream, MpegTSPacketizerSection * section) { guint8 tmp; guint8 *data, *crc_data; MpegTSPacketizerStreamSubtable *subtable; GSList *subtable_list = NULL; section->complete = TRUE; /* get the section buffer, ownership stays with the stream */ data = section->data = stream->section_data; section->offset = stream->offset; GST_MEMDUMP ("section header", data, stream->section_length); /* table_id : 8 bits * NOTE : Already parsed/stored in _push_section() */ section->table_id = stream->section_table_id; data += 1; /* section_syntax_indicator : 1 bit * private_indicator : 1 bit * RESERVED : 2 bit * private_section_length : 12 bit */ /* if table_id is 0 (pat) then ignore the subtable extension */ if ((data[0] & 0x80) == 0 || section->table_id == 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); } /* private_section_length : 12 bit * NOTE : Already parsed/stored in _push_section() * NOTE : Same as private_section_length mentionned above */ section->section_length = stream->section_length; data += 2; /* transport_stream_id : 16 bit */ /* skip to the version byte */ data += 2; /* Reserved : 2 bits * version_number : 5 bits * current_next_indicator : 1 bit*/ tmp = *data++; section->version_number = (tmp >> 1) & 0x1F; section->current_next_indicator = tmp & 0x01; if (!section->current_next_indicator) goto not_applicable; /* CRC is at the end of the section */ crc_data = section->data + section->section_length - 4; section->crc = GST_READ_UINT32_BE (crc_data); if (section->version_number == subtable->version_number && section->crc == subtable->crc) goto no_changes; subtable->version_number = section->version_number; subtable->crc = section->crc; stream->section_table_id = section->table_id; return TRUE; no_changes: GST_LOG ("no changes. pid 0x%04x table_id 0x%02x subtable_extension %d, current_next %d version %d, crc 0x%x", section->pid, section->table_id, section->subtable_extension, section->current_next_indicator, section->version_number, section->crc); section->complete = FALSE; return TRUE; not_applicable: GST_LOG ("not applicable pid 0x%04x table_id 0x%02x subtable_extension %d, current_next %d version %d, crc 0x%x", section->pid, section->table_id, section->subtable_extension, section->current_next_indicator, section->version_number, section->crc); section->complete = FALSE; return TRUE; } static inline void set_descriptors_array_on_structure (GstStructure * structure, GQuark quark, GValueArray * descriptors) { GValue value = { 0 }; g_value_init (&value, G_TYPE_VALUE_ARRAY); g_value_take_boxed (&value, descriptors); gst_structure_id_take_value (structure, quark, &value); } static GValueArray * mpegts_packetizer_parse_descriptors (MpegTSPacketizer2 * packetizer, guint8 ** buffer, guint8 * buffer_end) { GValueArray *descriptors = NULL; guint8 length; guint8 *data; GString *desc; guint i, nb_desc = 0; data = *buffer; while (data < buffer_end) { data++; /* skip tag */ length = *data++; if (data + length > buffer_end) { GST_WARNING ("invalid descriptor length %d now at %d max %d", length, (gint) (data - *buffer), (gint) (buffer_end - *buffer)); goto error; } data += length; nb_desc++; } if (data != buffer_end) { GST_WARNING ("descriptors size %d expected %d", (gint) (data - *buffer), (gint) (buffer_end - *buffer)); goto error; } data = *buffer; descriptors = g_value_array_new (nb_desc); for (i = 0; i < nb_desc; i++) { GValue *value = &(descriptors->values[i]); data++; /* skip tag */ length = *data++; /* include length */ desc = g_string_new_len ((gchar *) data - 2, length + 2); data += length; /* G_TYPE_GSTRING 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); } *buffer = data; return descriptors; error: return NULL; } GstStructure * mpegts_packetizer_parse_cat (MpegTSPacketizer2 * packetizer, MpegTSPacketizerSection * section) { GstStructure *cat_info = NULL; guint8 *data; guint8 tmp; GValueArray *descriptors; GstMPEGDescriptor desc; guint desc_len; /* Skip parts already parsed */ data = section->data + 3; /* reserved : 18bits */ data += 2; /* version_number : 5 bits * current_next_indicator : 1 bit */ tmp = *data++; section->version_number = (tmp >> 1) & 0x1F; section->current_next_indicator = tmp & 0x01; /* skip already handled section_number and last_section_number */ data += 2; cat_info = gst_structure_new_id_empty (QUARK_CAT); /* descriptors */ desc_len = section->section_length - 4 - 8; gst_mpeg_descriptor_parse (&desc, data, desc_len); descriptors = mpegts_packetizer_parse_descriptors (packetizer, &data, data + desc_len); if (descriptors == NULL) goto error; set_descriptors_array_on_structure (cat_info, QUARK_DESCRIPTORS, descriptors); return cat_info; error: if (cat_info) gst_structure_free (cat_info); return NULL; } GstStructure * mpegts_packetizer_parse_pat (MpegTSPacketizer2 * 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 = section->data; data += 3; 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_id (QUARK_PAT, QUARK_TRANSPORT_STREAM_ID, G_TYPE_UINT, transport_stream_id, NULL); g_value_init (&entries, GST_TYPE_LIST); /* stop at the CRC */ end = section->data + section->section_length; 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_empty (struct_name); g_free (struct_name); gst_structure_id_set (entry, QUARK_PROGRAM_NUMBER, G_TYPE_UINT, program_number, QUARK_PID, G_TYPE_UINT, pmt_pid, NULL); g_value_init (&value, GST_TYPE_STRUCTURE); g_value_take_boxed (&value, entry); gst_value_list_append_and_take_value (&entries, &value); } gst_structure_id_take_value (pat_info, QUARK_PROGRAMS, &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 (MpegTSPacketizer2 * 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 (section->section_length < 16) { GST_WARNING ("PID %d invalid PMT size %d", section->pid, section->section_length); goto error; } data = section->data; end = data + section->section_length; data += 3; program_number = GST_READ_UINT16_BE (data); data += 2; GST_DEBUG ("Parsing %d Program Map Table", program_number); 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; pmt = gst_structure_new_id (QUARK_PMT, QUARK_PROGRAM_NUMBER, G_TYPE_UINT, program_number, QUARK_PCR_PID, G_TYPE_UINT, pcr_pid, QUARK_VERSION_NUMBER, G_TYPE_UINT, section->version_number, NULL); 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, (gint) (end - data)); goto error; } descriptors = mpegts_packetizer_parse_descriptors (packetizer, &data, data + program_info_length); if (descriptors == NULL) goto error; set_descriptors_array_on_structure (pmt, QUARK_DESCRIPTORS, 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++; GST_DEBUG ("Stream type 0x%02x found", stream_type); 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, (gint) (end - data)); g_value_unset (&programs); goto error; } struct_name = g_strdup_printf ("pid-%d", pid); stream_info = gst_structure_new_empty (struct_name); g_free (struct_name); gst_structure_id_set (stream_info, QUARK_PID, G_TYPE_UINT, pid, QUARK_STREAM_TYPE, G_TYPE_UINT, stream_type, NULL); if (stream_info_length) { /* check for AC3 descriptor */ GstMPEGDescriptor desc; if (gst_mpeg_descriptor_parse (&desc, data, stream_info_length)) { /* DVB AC3 */ guint8 *desc_data; if (gst_mpeg_descriptor_find (&desc, DESC_DVB_AC3)) { gst_structure_set (stream_info, "has-ac3", G_TYPE_BOOLEAN, TRUE, NULL); } /* DATA BROADCAST ID */ desc_data = gst_mpeg_descriptor_find (&desc, DESC_DVB_DATA_BROADCAST_ID); if (desc_data) { guint16 data_broadcast_id; data_broadcast_id = DESC_DVB_DATA_BROADCAST_ID_data_broadcast_id (desc_data); gst_structure_set (stream_info, "data-broadcast-id", G_TYPE_UINT, data_broadcast_id, NULL); } /* DATA BROADCAST */ desc_data = gst_mpeg_descriptor_find (&desc, DESC_DVB_DATA_BROADCAST); if (desc_data) { GstStructure *databroadcast_info; guint16 data_broadcast_id; guint8 component_tag; data_broadcast_id = DESC_DVB_DATA_BROADCAST_data_broadcast_id (desc_data); component_tag = DESC_DVB_DATA_BROADCAST_component_tag (desc_data); databroadcast_info = gst_structure_new ("data-broadcast", "id", G_TYPE_UINT, data_broadcast_id, "component-tag", component_tag, NULL); gst_structure_set (stream_info, "data-broadcast", GST_TYPE_STRUCTURE, databroadcast_info, NULL); } /* DVB CAROUSEL IDENTIFIER */ desc_data = gst_mpeg_descriptor_find (&desc, DESC_DVB_CAROUSEL_IDENTIFIER); if (desc_data) { guint32 carousel_id; carousel_id = DESC_DVB_CAROUSEL_IDENTIFIER_carousel_id (desc_data); gst_structure_set (stream_info, "carousel-id", G_TYPE_UINT, carousel_id, NULL); } /* DVB STREAM IDENTIFIER */ desc_data = gst_mpeg_descriptor_find (&desc, DESC_DVB_STREAM_IDENTIFIER); if (desc_data) { guint8 component_tag; component_tag = DESC_DVB_STREAM_IDENTIFIER_component_tag (desc_data); gst_structure_set (stream_info, "component-tag", G_TYPE_UINT, component_tag, NULL); } /* ISO 639 LANGUAGE */ desc_data = gst_mpeg_descriptor_find (&desc, DESC_ISO_639_LANGUAGE); if (!desc_data) { desc_data = gst_mpeg_descriptor_find (&desc, DESC_DVB_SUBTITLING); } if (desc_data && DESC_ISO_639_LANGUAGE_codes_n (desc_data)) { gchar *lang_code; gchar *language_n = (gchar *) DESC_ISO_639_LANGUAGE_language_code_nth (desc_data, 0); lang_code = g_strndup (language_n, 3); gst_structure_set (stream_info, "lang-code", G_TYPE_STRING, lang_code, NULL); g_free (lang_code); } descriptors = mpegts_packetizer_parse_descriptors (packetizer, &data, data + stream_info_length); if (descriptors == NULL) { g_value_unset (&programs); gst_structure_free (stream_info); goto error; } set_descriptors_array_on_structure (stream_info, QUARK_DESCRIPTORS, descriptors); } } g_value_init (&stream_value, GST_TYPE_STRUCTURE); g_value_take_boxed (&stream_value, stream_info); gst_value_list_append_and_take_value (&programs, &stream_value); } gst_structure_id_take_value (pmt, QUARK_STREAMS, &programs); g_assert (data == end - 4); return pmt; error: if (pmt) gst_structure_free (pmt); return NULL; } GstStructure * mpegts_packetizer_parse_nit (MpegTSPacketizer2 * 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; GST_DEBUG ("NIT"); /* fixed header + CRC == 16 */ if (section->section_length < 23) { GST_WARNING ("PID %d invalid NIT size %d", section->pid, section->section_length); goto error; } data = section->data; end = data + section->section_length; data += 3; 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_id (QUARK_NIT, QUARK_NETWORK_ID, G_TYPE_UINT, network_id, QUARK_VERSION_NUMBER, G_TYPE_UINT, section->version_number, QUARK_CURRENT_NEXT_INDICATOR, G_TYPE_UINT, section->current_next_indicator, QUARK_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; } if (gst_mpeg_descriptor_parse (&mpegdescriptor, 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 (packetizer, networkname, networkname_length); gst_structure_id_set (nit, QUARK_NETWORK_NAME, G_TYPE_STRING, networkname_tmp, NULL); g_free (networkname_tmp); } descriptors = mpegts_packetizer_parse_descriptors (packetizer, &data, data + descriptors_loop_length); if (!descriptors) { gst_structure_free (nit); goto error; } set_descriptors_array_on_structure (nit, QUARK_DESCRIPTORS, 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_empty (transport_name); g_free (transport_name); gst_structure_id_set (transport, QUARK_TRANSPORT_STREAM_ID, G_TYPE_UINT, transport_stream_id, QUARK_ORIGINAL_NETWORK_ID, G_TYPE_UINT, original_network_id, NULL); 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; } gst_mpeg_descriptor_parse (&mpegdescriptor, 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); const gchar *polarization_str; guint8 modulation = DESC_DVB_SATELLITE_DELIVERY_SYSTEM_modulation (delivery); const 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); const 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 = "auto"; break; case 0x01: modulation_str = "QPSK"; break; case 0x02: modulation_str = "8PSK"; break; case 0x03: modulation_str = "QAM16"; break; default: modulation_str = ""; break; } 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_id_set (transport, QUARK_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); const 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 3 */ switch (guard_interval) { case 0: guard_interval = 32; break; case 1: guard_interval = 16; break; case 2: guard_interval = 8; break; case 3: 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_id (QUARK_TERRESTRIAL, QUARK_FREQUENCY, G_TYPE_UINT, frequency, QUARK_BANDWIDTH, G_TYPE_UINT, bandwidth, QUARK_CONSTELLATION, G_TYPE_STRING, constellation_str, QUARK_HIERARCHY, G_TYPE_UINT, hierarchy, QUARK_CODE_RATE_HP, G_TYPE_STRING, code_rate_hp_str, QUARK_CODE_RATE_LP, G_TYPE_STRING, code_rate_lp_str, QUARK_GUARD_INTERVAL, G_TYPE_UINT, guard_interval, QUARK_TRANSMISSION_MODE, G_TYPE_STRING, transmission_mode_str, QUARK_OTHER_FREQUENCY, G_TYPE_BOOLEAN, other_frequency, NULL); gst_structure_id_set (transport, QUARK_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); const 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); const 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_id (QUARK_CABLE, QUARK_MODULATION, G_TYPE_STRING, modulation_str, QUARK_FREQUENCY, G_TYPE_UINT, frequency, QUARK_SYMBOL_RATE, G_TYPE_UINT, symbol_rate, QUARK_INNER_FEC, G_TYPE_STRING, fec_inner_str, NULL); gst_structure_id_set (transport, QUARK_DELIVERY, GST_TYPE_STRUCTURE, delivery_structure, NULL); } /* free the temporary delivery structure */ if (delivery_structure != NULL) { gst_structure_free (delivery_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_id (QUARK_CHANNELS, QUARK_SERVICE_ID, G_TYPE_UINT, service_id, QUARK_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_and_take_value (&channel_numbers, &channel_value); current_pos += 2; } gst_structure_id_take_value (transport, QUARK_CHANNELS, &channel_numbers); } if ((delivery = gst_mpeg_descriptor_find (&mpegdescriptor, DESC_DVB_FREQUENCY_LIST))) { guint8 *current_pos = delivery + 2; GValue frequencies = { 0 }; guint8 type; type = *current_pos & 0x03; current_pos++; if (type) { const gchar *fieldname = NULL; g_value_init (&frequencies, GST_TYPE_LIST); while (current_pos < delivery + DESC_LENGTH (delivery) - 3) { guint32 freq = 0; guint8 *frequency_bcd = current_pos; GValue frequency = { 0 }; switch (type) { case 0x01: /* satellite */ freq = 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)); break; case 0x02: /* cable */ freq = 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)); break; case 0x03: /* terrestrial */ freq = GST_READ_UINT32_BE (current_pos) * 10; break; } g_value_init (&frequency, G_TYPE_UINT); g_value_set_uint (&frequency, freq); gst_value_list_append_and_take_value (&frequencies, &frequency); current_pos += 4; } switch (type) { case 0x01: fieldname = "frequency-list-satellite"; break; case 0x02: fieldname = "frequency-list-cable"; break; case 0x03: fieldname = "frequency-list-terrestrial"; break; } gst_structure_take_value (transport, fieldname, &frequencies); } } descriptors = mpegts_packetizer_parse_descriptors (packetizer, &data, data + descriptors_loop_length); if (!descriptors) { gst_structure_free (transport); goto error; } set_descriptors_array_on_structure (transport, QUARK_DESCRIPTORS, descriptors); } g_value_init (&transport_value, GST_TYPE_STRUCTURE); g_value_take_boxed (&transport_value, transport); gst_value_list_append_and_take_value (&transports, &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, (gint) (data - section->data), section->section_length); goto error; } gst_structure_id_take_value (nit, QUARK_TRANSPORTS, &transports); GST_DEBUG ("NIT %" GST_PTR_FORMAT, nit); 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 (MpegTSPacketizer2 * 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; guint8 running_status; gboolean scrambled; guint descriptors_loop_length; GValue services = { 0 }; GValueArray *descriptors = NULL; GValue service_value = { 0 }; GST_DEBUG ("SDT"); /* fixed header + CRC == 16 */ if (section->section_length < 14) { GST_WARNING ("PID %d invalid SDT size %d", section->pid, section->section_length); goto error; } data = section->data; end = data + section->section_length; data += 3; 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_id (QUARK_SDT, QUARK_TRANSPORT_STREAM_ID, G_TYPE_UINT, transport_stream_id, QUARK_VERSION_NUMBER, G_TYPE_UINT, section->version_number, QUARK_CURRENT_NEXT_INDICATOR, G_TYPE_UINT, section->current_next_indicator, QUARK_ORIGINAL_NETWORK_ID, G_TYPE_UINT, original_network_id, QUARK_ACTUAL_TRANSPORT_STREAM, G_TYPE_BOOLEAN, section->table_id == 0x42, NULL); sdt_info_length = section->section_length - 11; 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; /* EIT_schedule = ((*data & 0x02) == 2); */ /* EIT_present_following = (*data & 0x01) == 1; */ data += 1; tmp = GST_READ_UINT16_BE (data); running_status = (*data >> 5) & 0x07; scrambled = (*data >> 4) & 0x01; descriptors_loop_length = tmp & 0x0FFF; data += 2; /* 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_empty (service_name); 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; } gst_mpeg_descriptor_parse (&mpegdescriptor, 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)) { const gchar *running_status_tmp; switch (running_status) { case 0: running_status_tmp = "undefined"; break; case 1: running_status_tmp = "not running"; break; case 2: running_status_tmp = "starts in a few seconds"; break; case 3: running_status_tmp = "pausing"; break; case 4: running_status_tmp = "running"; break; default: running_status_tmp = "reserved"; } servicename_tmp = get_encoding_and_convert (packetizer, servicename, servicename_length); serviceprovider_name_tmp = get_encoding_and_convert (packetizer, serviceprovider_name, serviceprovider_name_length); gst_structure_set (service, "name", G_TYPE_STRING, servicename_tmp, "provider-name", G_TYPE_STRING, serviceprovider_name_tmp, "scrambled", G_TYPE_BOOLEAN, scrambled, "running-status", G_TYPE_STRING, running_status_tmp, NULL); g_free (servicename_tmp); g_free (serviceprovider_name_tmp); } } descriptors = mpegts_packetizer_parse_descriptors (packetizer, &data, data + descriptors_loop_length); if (!descriptors) { gst_structure_free (service); goto error; } set_descriptors_array_on_structure (service, QUARK_DESCRIPTORS, descriptors); } g_value_init (&service_value, GST_TYPE_STRUCTURE); g_value_take_boxed (&service_value, service); gst_value_list_append_and_take_value (&services, &service_value); sdt_info_length -= data - entry_begin; } if (data != end - 4) { GST_WARNING ("PID %d invalid SDT parsed %d length %d", section->pid, (gint) (data - section->data), section->section_length); goto error; } gst_structure_id_take_value (sdt, QUARK_SERVICES, &services); return sdt; error: if (sdt) gst_structure_free (sdt); if (GST_VALUE_HOLDS_LIST (&services)) g_value_unset (&services); return NULL; } /* FIXME : Can take up to 50% of total mpeg-ts demuxing cpu usage */ GstStructure * mpegts_packetizer_parse_eit (MpegTSPacketizer2 * 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; 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 *event_name; guint tmp; /* fixed header + CRC == 16 */ if (section->section_length < 18) { GST_WARNING ("PID %d invalid EIT size %d", section->pid, section->section_length); goto error; } data = section->data; end = data + section->section_length; data += 3; 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_id (QUARK_EIT, QUARK_VERSION_NUMBER, G_TYPE_UINT, section->version_number, QUARK_CURRENT_NEXT_INDICATOR, G_TYPE_UINT, section->current_next_indicator, QUARK_SERVICE_ID, G_TYPE_UINT, service_id, QUARK_ACTUAL_TRANSPORT_STREAM, G_TYPE_BOOLEAN, (section->table_id == 0x4E || (section->table_id >= 0x50 && section->table_id <= 0x5F)), QUARK_PRESENT_FOLLOWING, G_TYPE_BOOLEAN, (section->table_id == 0x4E || section->table_id == 0x4F), QUARK_TRANSPORT_STREAM_ID, G_TYPE_UINT, transport_stream_id, QUARK_ORIGINAL_NETWORK_ID, G_TYPE_UINT, original_network_id, QUARK_SEGMENT_LAST_SECTION_NUMBER, G_TYPE_UINT, segment_last_section_number, QUARK_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, (gint) (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_empty (event_name); g_free (event_name); gst_structure_id_set (event, QUARK_EVENT_ID, G_TYPE_UINT, event_id, QUARK_YEAR, G_TYPE_UINT, year, QUARK_MONTH, G_TYPE_UINT, month, QUARK_DAY, G_TYPE_UINT, day, QUARK_HOUR, G_TYPE_UINT, hour, QUARK_MINUTE, G_TYPE_UINT, minute, QUARK_SECOND, G_TYPE_UINT, second, QUARK_DURATION, G_TYPE_UINT, duration, QUARK_RUNNING_STATUS, G_TYPE_UINT, running_status, QUARK_FREE_CA_MODE, G_TYPE_BOOLEAN, free_ca_mode, NULL); 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; } gst_mpeg_descriptor_parse (&mpegdescriptor, 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 (packetizer, eventname, eventname_length); eventdescription_tmp = get_encoding_and_convert (packetizer, eventdescription, eventdescription_length); gst_structure_id_set (event, QUARK_NAME, G_TYPE_STRING, eventname_tmp, QUARK_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 }; GValue extended_item_value = { 0 }; GstStructure *extended_item; gchar *extended_text = NULL; 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) { guint8 *items_aux = DESC_DVB_EXTENDED_EVENT_items (extended_descriptor); guint8 *items_limit = items_aux + DESC_DVB_EXTENDED_EVENT_items_length (extended_descriptor); while (items_aux < items_limit) { guint8 length_aux; gchar *description, *text; /* Item Description text */ length_aux = *items_aux; ++items_aux; description = get_encoding_and_convert (packetizer, (gchar *) items_aux, length_aux); items_aux += length_aux; /* Item text */ length_aux = *items_aux; ++items_aux; text = get_encoding_and_convert (packetizer, (gchar *) items_aux, length_aux); items_aux += length_aux; extended_item = gst_structure_new_id (QUARK_EXTENDED_ITEM, QUARK_DESCRIPTION, G_TYPE_STRING, description, QUARK_TEXT, G_TYPE_STRING, text, NULL); g_value_init (&extended_item_value, GST_TYPE_STRUCTURE); g_value_take_boxed (&extended_item_value, extended_item); gst_value_list_append_and_take_value (&extended_items, &extended_item_value); } if (extended_text) { gchar *tmp; gchar *old_extended_text = extended_text; tmp = get_encoding_and_convert (packetizer, (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 = get_encoding_and_convert (packetizer, (gchar *) DESC_DVB_EXTENDED_EVENT_text (extended_descriptor), DESC_DVB_EXTENDED_EVENT_text_length (extended_descriptor)); } } } if (extended_text) { gst_structure_id_set (event, QUARK_EXTENDED_TEXT, G_TYPE_STRING, extended_text, NULL); g_free (extended_text); } gst_structure_id_take_value (event, QUARK_EXTENDED_ITEMS, &extended_items); 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; const 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, "tag", G_TYPE_INT, DESC_DVB_COMPONENT_tag (comp_descriptor), 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, "tag", G_TYPE_INT, DESC_DVB_COMPONENT_tag (comp_descriptor), 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, "tag", G_TYPE_INT, DESC_DVB_COMPONENT_tag (comp_descriptor), NULL); break; } if (component) { g_value_init (&component_value, GST_TYPE_STRUCTURE); g_value_take_boxed (&component_value, component); gst_value_list_append_and_take_value (&components, &component_value); component = NULL; } } gst_structure_take_value (event, "components", &components); g_array_free (component_descriptors, TRUE); } descriptors = mpegts_packetizer_parse_descriptors (packetizer, &data, data + descriptors_loop_length); if (!descriptors) { gst_structure_free (event); goto error; } set_descriptors_array_on_structure (event, QUARK_DESCRIPTORS, descriptors); } g_value_init (&event_value, GST_TYPE_STRUCTURE); g_value_take_boxed (&event_value, event); gst_value_list_append_and_take_value (&events, &event_value); } if (data != end - 4) { GST_WARNING ("PID %d invalid EIT parsed %d length %d", section->pid, (gint) (data - section->data), section->section_length); goto error; } gst_structure_id_take_value (eit, QUARK_EVENTS, &events); GST_DEBUG ("EIT %" GST_PTR_FORMAT, eit); return eit; error: if (eit) gst_structure_free (eit); if (GST_VALUE_HOLDS_LIST (&events)) g_value_unset (&events); return NULL; } static GstStructure * parse_tdt_tot_common (MpegTSPacketizer2 * packetizer, MpegTSPacketizerSection * section, const gchar * name) { GstStructure *res; guint16 mjd; guint year, month, day, hour, minute, second; guint8 *data, *utc_ptr; /* length at least 8 */ if (section->section_length < 8) { GST_WARNING ("PID %d invalid TDT/TOT size %d", section->pid, section->section_length); return NULL; } data = section->data; data += 3; mjd = GST_READ_UINT16_BE (data); data += 2; utc_ptr = 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); } res = gst_structure_new (name, "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, NULL); return res; } GstStructure * mpegts_packetizer_parse_tdt (MpegTSPacketizer2 * packetizer, MpegTSPacketizerSection * section) { GstStructure *tdt = NULL; GST_DEBUG ("TDT"); tdt = parse_tdt_tot_common (packetizer, section, "tdt"); return tdt; } GstStructure * mpegts_packetizer_parse_tot (MpegTSPacketizer2 * packetizer, MpegTSPacketizerSection * section) { guint8 *data; GstStructure *tot = NULL; GValueArray *descriptors; guint16 desc_len; GST_DEBUG ("TOT"); tot = parse_tdt_tot_common (packetizer, section, "tot"); data = section->data + 8; desc_len = ((*data++) & 0xf) << 8; desc_len |= *data++; descriptors = mpegts_packetizer_parse_descriptors (packetizer, &data, data + desc_len); if (!descriptors) { gst_structure_free (tot); return NULL; } gst_structure_id_set (tot, QUARK_DESCRIPTORS, G_TYPE_VALUE_ARRAY, descriptors, NULL); g_value_array_free (descriptors); return tot; } void mpegts_packetizer_clear (MpegTSPacketizer2 * packetizer) { if (packetizer->know_packet_size) { packetizer->know_packet_size = FALSE; packetizer->packet_size = 0; if (packetizer->caps != NULL) { gst_caps_unref (packetizer->caps); packetizer->caps = NULL; } } if (packetizer->streams) { int i; for (i = 0; i < 8192; i++) { if (packetizer->streams[i]) { mpegts_packetizer_stream_free (packetizer->streams[i]); } } memset (packetizer->streams, 0, 8192 * sizeof (MpegTSPacketizerStream *)); } gst_adapter_clear (packetizer->adapter); packetizer->offset = 0; packetizer->empty = TRUE; packetizer->priv->available = 0; packetizer->priv->mapped = NULL; packetizer->priv->mapped_size = 0; packetizer->priv->offset = 0; packetizer->priv->last_in_time = GST_CLOCK_TIME_NONE; } void mpegts_packetizer_flush (MpegTSPacketizer2 * packetizer) { GST_DEBUG ("Flushing"); if (packetizer->streams) { int i; for (i = 0; i < 8192; i++) { if (packetizer->streams[i]) { mpegts_packetizer_clear_section (packetizer->streams[i]); } } } gst_adapter_clear (packetizer->adapter); packetizer->offset = 0; packetizer->empty = TRUE; packetizer->priv->available = 0; packetizer->priv->mapped = NULL; packetizer->priv->offset = 0; packetizer->priv->mapped_size = 0; packetizer->priv->last_in_time = GST_CLOCK_TIME_NONE; flush_observations (packetizer); } void mpegts_packetizer_remove_stream (MpegTSPacketizer2 * packetizer, gint16 pid) { MpegTSPacketizerStream *stream = packetizer->streams[pid]; if (stream) { GST_INFO ("Removing stream for PID %d", pid); mpegts_packetizer_stream_free (stream); packetizer->streams[pid] = NULL; } } MpegTSPacketizer2 * mpegts_packetizer_new (void) { MpegTSPacketizer2 *packetizer; packetizer = GST_MPEGTS_PACKETIZER (g_object_new (GST_TYPE_MPEGTS_PACKETIZER, NULL)); return packetizer; } void mpegts_packetizer_push (MpegTSPacketizer2 * packetizer, GstBuffer * buffer) { if (G_UNLIKELY (packetizer->empty)) { packetizer->empty = FALSE; packetizer->offset = GST_BUFFER_OFFSET (buffer); } GST_DEBUG ("Pushing %" G_GSIZE_FORMAT " byte from offset %" G_GUINT64_FORMAT, gst_buffer_get_size (buffer), GST_BUFFER_OFFSET (buffer)); gst_adapter_push (packetizer->adapter, buffer); packetizer->priv->available += gst_buffer_get_size (buffer); /* If buffer timestamp is valid, store it */ if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer))) packetizer->priv->last_in_time = GST_BUFFER_TIMESTAMP (buffer); } static gboolean mpegts_try_discover_packet_size (MpegTSPacketizer2 * packetizer) { guint8 *dest; int i, pos = -1, j; static const guint psizes[] = { MPEGTS_NORMAL_PACKETSIZE, MPEGTS_M2TS_PACKETSIZE, MPEGTS_DVB_ASI_PACKETSIZE, MPEGTS_ATSC_PACKETSIZE }; dest = g_malloc (MPEGTS_MAX_PACKETSIZE * 4); /* wait for 3 sync bytes */ while (packetizer->priv->available >= MPEGTS_MAX_PACKETSIZE * 4) { /* check for sync bytes */ gst_adapter_copy (packetizer->adapter, dest, 0, MPEGTS_MAX_PACKETSIZE * 4); /* find first sync byte */ pos = -1; for (i = 0; i < MPEGTS_MAX_PACKETSIZE; i++) { if (dest[i] == PACKET_SYNC_BYTE) { for (j = 0; j < 4; j++) { guint packetsize = psizes[j]; /* check each of the packet size possibilities in turn */ if (dest[i] == PACKET_SYNC_BYTE && dest[i + packetsize] == PACKET_SYNC_BYTE && dest[i + packetsize * 2] == PACKET_SYNC_BYTE && dest[i + packetsize * 3] == PACKET_SYNC_BYTE) { packetizer->know_packet_size = TRUE; packetizer->packet_size = packetsize; packetizer->caps = gst_caps_new_simple ("video/mpegts", "systemstream", G_TYPE_BOOLEAN, TRUE, "packetsize", G_TYPE_INT, packetsize, NULL); if (packetsize == MPEGTS_M2TS_PACKETSIZE) pos = i - 4; else pos = i; break; } } break; } } if (packetizer->know_packet_size) break; /* Skip MPEGTS_MAX_PACKETSIZE */ gst_adapter_flush (packetizer->adapter, MPEGTS_MAX_PACKETSIZE); packetizer->priv->available -= MPEGTS_MAX_PACKETSIZE; packetizer->offset += MPEGTS_MAX_PACKETSIZE; } g_free (dest); if (packetizer->know_packet_size) { GST_DEBUG ("have packetsize detected: %d of %u bytes", packetizer->know_packet_size, packetizer->packet_size); /* flush to sync byte */ if (pos > 0) { GST_DEBUG ("Flushing out %d bytes", pos); gst_adapter_flush (packetizer->adapter, pos); packetizer->offset += pos; packetizer->priv->available -= MPEGTS_MAX_PACKETSIZE; } } else { /* drop invalid data and move to the next possible packets */ GST_DEBUG ("Could not determine packet size"); } return packetizer->know_packet_size; } gboolean mpegts_packetizer_has_packets (MpegTSPacketizer2 * packetizer) { if (G_UNLIKELY (packetizer->know_packet_size == FALSE)) { if (!mpegts_try_discover_packet_size (packetizer)) return FALSE; } return packetizer->priv->available >= packetizer->packet_size; } MpegTSPacketizerPacketReturn mpegts_packetizer_next_packet (MpegTSPacketizer2 * packetizer, MpegTSPacketizerPacket * packet) { MpegTSPacketizerPrivate *priv = packetizer->priv; guint avail; int i; if (G_UNLIKELY (!packetizer->know_packet_size)) { if (!mpegts_try_discover_packet_size (packetizer)) return PACKET_NEED_MORE; } while ((avail = priv->available) >= packetizer->packet_size) { if (priv->mapped == NULL) { priv->mapped_size = priv->available - (priv->available % packetizer->packet_size); priv->mapped = (guint8 *) gst_adapter_map (packetizer->adapter, priv->mapped_size); priv->offset = 0; } packet->data_start = priv->mapped + priv->offset; /* M2TS packets don't start with the sync byte, all other variants do */ if (packetizer->packet_size == MPEGTS_M2TS_PACKETSIZE) packet->data_start += 4; /* ALL mpeg-ts variants contain 188 bytes of data. Those with bigger packet * sizes contain either extra data (timesync, FEC, ..) either before or after * the data */ packet->data_end = packet->data_start + 188; packet->offset = packetizer->offset; GST_LOG ("offset %" G_GUINT64_FORMAT, packet->offset); packetizer->offset += packetizer->packet_size; GST_MEMDUMP ("data_start", packet->data_start, 16); packet->origts = priv->last_in_time; /* Check sync byte */ if (G_LIKELY (packet->data_start[0] == 0x47)) goto got_valid_packet; GST_LOG ("Lost sync %d", packetizer->packet_size); /* Find the 0x47 in the buffer */ for (i = 0; i < packetizer->packet_size; i++) if (packet->data_start[i] == 0x47) break; if (packetizer->packet_size == MPEGTS_M2TS_PACKETSIZE) { if (i >= 4) i -= 4; else i += 188; } GST_DEBUG ("Flushing %d bytes out", i); /* gst_adapter_flush (packetizer->adapter, i); */ /* Pop out the remaining data... */ priv->offset += i; priv->available -= i; if (G_UNLIKELY (priv->available < packetizer->packet_size)) { GST_DEBUG ("Flushing %d bytes out", priv->offset); gst_adapter_flush (packetizer->adapter, priv->offset); priv->mapped = NULL; } continue; } return PACKET_NEED_MORE; got_valid_packet: return mpegts_packetizer_parse_packet (packetizer, packet); } MpegTSPacketizerPacketReturn mpegts_packetizer_process_next_packet (MpegTSPacketizer2 * packetizer) { MpegTSPacketizerPacket packet; MpegTSPacketizerPacketReturn ret; ret = mpegts_packetizer_next_packet (packetizer, &packet); if (ret != PACKET_NEED_MORE) { packetizer->priv->offset += packetizer->packet_size; packetizer->priv->available -= packetizer->packet_size; if (G_UNLIKELY (packetizer->priv->available < packetizer->packet_size)) { gst_adapter_flush (packetizer->adapter, packetizer->priv->offset); packetizer->priv->mapped = NULL; } } return ret; } void mpegts_packetizer_clear_packet (MpegTSPacketizer2 * packetizer, MpegTSPacketizerPacket * packet) { MpegTSPacketizerPrivate *priv = packetizer->priv; priv->offset += packetizer->packet_size; priv->available -= packetizer->packet_size; if (G_UNLIKELY (priv->mapped && priv->available < packetizer->packet_size)) { gst_adapter_flush (packetizer->adapter, priv->offset); priv->mapped = NULL; } } gboolean mpegts_packetizer_push_section (MpegTSPacketizer2 * packetizer, MpegTSPacketizerPacket * packet, MpegTSPacketizerSection * section) { gboolean res = FALSE; MpegTSPacketizerStream *stream; guint8 pointer, table_id; guint16 subtable_extension; guint section_length; guint8 *data, *data_start; 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 0x%04x PSI section pointer points past the end " "of the buffer", packet->pid); goto out; } data += pointer; } GST_MEMDUMP ("section data", packet->data, packet->data_end - packet->data); /* TDT and TOT sections (see ETSI EN 300 468 5.2.5) * these sections do not extend to several packets so we don't need to use the * sections filter. */ if (packet->pid == 0x14) { section->offset = packet->offset; table_id = data[0]; section->section_length = (GST_READ_UINT24_BE (data) & 0x000FFF) + 3; if (data + section->section_length > packet->data_end) { GST_WARNING ("PID 0x%04x PSI section length extends past the end " "of the buffer", packet->pid); goto out; } section->data = data; section->table_id = table_id; section->complete = TRUE; res = TRUE; GST_DEBUG ("TDT section pid:0x%04x table_id:0x%02x section_length: %d", packet->pid, table_id, section->section_length); goto out; } data_start = data; stream = packetizer->streams[packet->pid]; if (stream == NULL) { stream = mpegts_packetizer_stream_new (); packetizer->streams[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: 0x%04x table_id 0x%02x sub_table_extension %d", packet->pid, table_id, subtable_extension); section_length = (GST_READ_UINT16_BE (data) & 0x0FFF) + 3; if (stream->continuity_counter != CONTINUITY_UNSET) { GST_DEBUG ("PID 0x%04x table_id 0x%02x sub_table_extension %d payload_unit_start_indicator set but section " "not complete (last_continuity: %d continuity: %d sec len %d", packet->pid, table_id, subtable_extension, stream->continuity_counter, packet->continuity_counter, section_length); mpegts_packetizer_clear_section (stream); } else { GST_DEBUG ("pusi set and new stream section is %d long and data we have is: %d", section_length, (gint) (packet->data_end - packet->data)); } stream->continuity_counter = packet->continuity_counter; stream->section_length = section_length; /* Create enough room to store chunks of sections, including FF padding */ if (stream->section_allocated == 0) { stream->section_data = g_malloc (section_length + 188); stream->section_allocated = section_length + 188; } else if (G_UNLIKELY (stream->section_allocated < section_length + 188)) { stream->section_data = g_realloc (stream->section_data, section_length + 188); stream->section_allocated = section_length + 188; } memcpy (stream->section_data, data_start, packet->data_end - data_start); stream->section_offset = packet->data_end - data_start; stream->section_table_id = table_id; stream->offset = packet->offset; 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; memcpy (stream->section_data + stream->section_offset, data_start, packet->data_end - data_start); stream->section_offset += packet->data_end - data_start; GST_DEBUG ("Appending data (need %d, have %d)", stream->section_length, stream->section_offset); res = TRUE; } else { if (stream->continuity_counter == CONTINUITY_UNSET) GST_DEBUG ("PID 0x%04x waiting for pusi", packet->pid); else GST_DEBUG ("PID 0x%04x section discontinuity " "(last_continuity: %d continuity: %d", packet->pid, stream->continuity_counter, packet->continuity_counter); mpegts_packetizer_clear_section (stream); } 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 (stream->section_offset >= stream->section_length) { res = mpegts_packetizer_parse_section_header (packetizer, stream, section); /* flush stuffing bytes */ mpegts_packetizer_clear_section (stream); } else { GST_DEBUG ("section not complete"); /* section not complete yet */ section->complete = FALSE; } } else { GST_WARNING ("section not complete"); section->complete = FALSE; } out: packet->data = data; GST_DEBUG ("result: %d complete: %d", res, section->complete); return res; } static void _init_local (void) { GST_DEBUG_CATEGORY_INIT (mpegts_packetizer_debug, "mpegtspacketizer", 0, "MPEG transport stream parser"); QUARK_PAT = g_quark_from_string ("pat"); QUARK_TRANSPORT_STREAM_ID = g_quark_from_string ("transport-stream-id"); QUARK_PROGRAM_NUMBER = g_quark_from_string ("program-number"); QUARK_PID = g_quark_from_string ("pid"); QUARK_PROGRAMS = g_quark_from_string ("programs"); QUARK_CAT = g_quark_from_string ("cat"); QUARK_PMT = g_quark_from_string ("pmt"); QUARK_PCR_PID = g_quark_from_string ("pcr-pid"); QUARK_VERSION_NUMBER = g_quark_from_string ("version-number"); QUARK_DESCRIPTORS = g_quark_from_string ("descriptors"); QUARK_STREAM_TYPE = g_quark_from_string ("stream-type"); QUARK_STREAMS = g_quark_from_string ("streams"); QUARK_NIT = g_quark_from_string ("nit"); QUARK_NETWORK_ID = g_quark_from_string ("network-id"); QUARK_CURRENT_NEXT_INDICATOR = g_quark_from_string ("current-next-indicator"); QUARK_ACTUAL_NETWORK = g_quark_from_string ("actual-network"); QUARK_NETWORK_NAME = g_quark_from_string ("network-name"); QUARK_ORIGINAL_NETWORK_ID = g_quark_from_string ("original-network-id"); QUARK_TRANSPORTS = g_quark_from_string ("transports"); QUARK_TERRESTRIAL = g_quark_from_string ("terrestrial"); QUARK_CABLE = g_quark_from_string ("cable"); QUARK_FREQUENCY = g_quark_from_string ("frequency"); QUARK_MODULATION = g_quark_from_string ("modulation"); QUARK_BANDWIDTH = g_quark_from_string ("bandwidth"); QUARK_CONSTELLATION = g_quark_from_string ("constellation"); QUARK_HIERARCHY = g_quark_from_string ("hierarchy"); QUARK_CODE_RATE_HP = g_quark_from_string ("code-rate-hp"); QUARK_CODE_RATE_LP = g_quark_from_string ("code-rate-lp"); QUARK_GUARD_INTERVAL = g_quark_from_string ("guard-interval"); QUARK_TRANSMISSION_MODE = g_quark_from_string ("transmission-mode"); QUARK_OTHER_FREQUENCY = g_quark_from_string ("other-frequency"); QUARK_SYMBOL_RATE = g_quark_from_string ("symbol-rate"); QUARK_INNER_FEC = g_quark_from_string ("inner-fec"); QUARK_DELIVERY = g_quark_from_string ("delivery"); QUARK_CHANNELS = g_quark_from_string ("channels"); QUARK_LOGICAL_CHANNEL_NUMBER = g_quark_from_string ("logical-channel-number"); QUARK_SDT = g_quark_from_string ("sdt"); QUARK_ACTUAL_TRANSPORT_STREAM = g_quark_from_string ("actual-transport-stream"); QUARK_SERVICES = g_quark_from_string ("services"); QUARK_EIT = g_quark_from_string ("eit"); QUARK_SERVICE_ID = g_quark_from_string ("service-id"); QUARK_PRESENT_FOLLOWING = g_quark_from_string ("present-following"); QUARK_SEGMENT_LAST_SECTION_NUMBER = g_quark_from_string ("segment-last-section-number"); QUARK_LAST_TABLE_ID = g_quark_from_string ("last-table-id"); QUARK_EVENTS = g_quark_from_string ("events"); QUARK_NAME = g_quark_from_string ("name"); QUARK_DESCRIPTION = g_quark_from_string ("description"); QUARK_EXTENDED_ITEM = g_quark_from_string ("extended_item"); QUARK_EXTENDED_ITEMS = g_quark_from_string ("extended-items"); QUARK_TEXT = g_quark_from_string ("text"); QUARK_EXTENDED_TEXT = g_quark_from_string ("extended-text"); QUARK_EVENT_ID = g_quark_from_string ("event-id"); QUARK_YEAR = g_quark_from_string ("year"); QUARK_MONTH = g_quark_from_string ("month"); QUARK_DAY = g_quark_from_string ("day"); QUARK_HOUR = g_quark_from_string ("hour"); QUARK_MINUTE = g_quark_from_string ("minute"); QUARK_SECOND = g_quark_from_string ("second"); QUARK_DURATION = g_quark_from_string ("duration"); QUARK_RUNNING_STATUS = g_quark_from_string ("running-status"); QUARK_FREE_CA_MODE = g_quark_from_string ("free-ca-mode"); } /** * @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: GIconv for conversion or NULL */ static LocalIconvCode get_encoding (MpegTSPacketizer2 * packetizer, const gchar * text, guint * start_text, gboolean * is_multibyte) { LocalIconvCode encoding; guint8 firstbyte; *is_multibyte = FALSE; *start_text = 0; firstbyte = (guint8) text[0]; /* A wrong value */ g_return_val_if_fail (firstbyte != 0x00, _ICONV_UNKNOWN); if (firstbyte <= 0x0B) { /* 0x01 => iso 8859-5 */ encoding = firstbyte + _ICONV_ISO8859_4; *start_text = 1; goto beach; } /* ETSI EN 300 468, "Selection of character table" */ switch (firstbyte) { case 0x0C: case 0x0D: case 0x0E: case 0x0F: /* RESERVED */ encoding = _ICONV_UNKNOWN; break; case 0x10: { guint16 table; table = GST_READ_UINT16_BE (text + 1); if (table < 17) encoding = _ICONV_UNKNOWN + table; else encoding = _ICONV_UNKNOWN;; *start_text = 3; break; } case 0x11: encoding = _ICONV_ISO10646_UC2; *start_text = 1; *is_multibyte = TRUE; break; case 0x12: /* EUC-KR implements KSX1001 */ encoding = _ICONV_EUC_KR; *start_text = 1; *is_multibyte = TRUE; break; case 0x13: encoding = _ICONV_GB2312; *start_text = 1; break; case 0x14: encoding = _ICONV_UTF_16BE; *start_text = 1; *is_multibyte = TRUE; break; case 0x15: /* TODO : Where does this come from ?? */ encoding = _ICONV_ISO10646_UTF8; *start_text = 1; break; case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: /* RESERVED */ encoding = _ICONV_UNKNOWN; break; default: encoding = _ICONV_ISO6937; break; } beach: GST_DEBUG ("Found encoding %d, first byte is 0x%02x, start_text: %u, is_multibyte: %d", encoding, firstbyte, *start_text, *is_multibyte); 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, GIConv iconv, gboolean is_multibyte, GError ** error) { gchar *new_text; gchar *tmp, *pos; gint i; text += start; pos = tmp = g_malloc (length * 2); if (is_multibyte) { if (length == -1) { while (*text != '\0') { guint16 code = GST_READ_UINT16_BE (text); switch (code) { case 0xE086: /* emphasis on */ case 0xE087: /* emphasis off */ /* skip it */ break; case 0xE08A:{ pos[0] = 0x00; /* 0x00 0x0A is a new line */ pos[1] = 0x0A; pos += 2; break; } default: pos[0] = text[0]; pos[1] = text[1]; pos += 2; break; } text += 2; } } else { for (i = 0; i < length; i += 2) { guint16 code = GST_READ_UINT16_BE (text); switch (code) { case 0xE086: /* emphasis on */ case 0xE087: /* emphasis off */ /* skip it */ break; case 0xE08A:{ pos[0] = 0x00; /* 0x00 0x0A is a new line */ pos[1] = 0x0A; pos += 2; break; } default: pos[0] = text[0]; pos[1] = text[1]; pos += 2; break; } text += 2; } } } else { if (length == -1) { while (*text != '\0') { guint8 code = (guint8) (*text); switch (code) { case 0x86: /* emphasis on */ case 0x87: /* emphasis off */ /* skip it */ break; case 0x8A: *pos = '\n'; pos += 1; break; default: *pos = *text; pos += 1; break; } text++; } } else { for (i = 0; i < length; i++) { guint8 code = (guint8) (*text); switch (code) { case 0x86: /* emphasis on */ case 0x87: /* emphasis off */ /* skip it */ break; case 0x8A: *pos = '\n'; pos += 1; break; default: *pos = *text; pos += 1; break; } text++; } } } if (pos > tmp) { gsize bread = 0; new_text = g_convert_with_iconv (tmp, pos - tmp, iconv, &bread, NULL, error); GST_DEBUG ("Converted to : %s", new_text); } else { new_text = g_strdup (""); } g_free (tmp); return new_text; } static gchar * get_encoding_and_convert (MpegTSPacketizer2 * packetizer, const gchar * text, guint length) { GError *error = NULL; gchar *converted_str; guint start_text = 0; gboolean is_multibyte; LocalIconvCode encoding; GIConv iconv = (GIConv) - 1; g_return_val_if_fail (text != NULL, NULL); if (text == NULL || length == 0) return g_strdup (""); encoding = get_encoding (packetizer, text, &start_text, &is_multibyte); if (encoding > _ICONV_UNKNOWN && encoding < _ICONV_MAX) { GST_DEBUG ("Encoding %s", iconvtablename[encoding]); if (packetizer->priv->iconvs[encoding] == (GIConv) - 1) packetizer->priv->iconvs[encoding] = g_iconv_open ("utf-8", iconvtablename[encoding]); iconv = packetizer->priv->iconvs[encoding]; } if (iconv == (GIConv) - 1) { GST_WARNING ("Could not detect encoding"); converted_str = g_strndup (text, length); goto beach; } converted_str = convert_to_utf8 (text, length - start_text, start_text, iconv, is_multibyte, &error); if (error != NULL) { GST_WARNING ("Could not convert string: %s", error->message); g_error_free (error); error = NULL; if (encoding >= _ICONV_ISO8859_2 && encoding <= _ICONV_ISO8859_15) { /* Sometimes using the standard 8859-1 set fixes issues */ GST_DEBUG ("Encoding %s", iconvtablename[_ICONV_ISO8859_1]); if (packetizer->priv->iconvs[_ICONV_ISO8859_1] == (GIConv) - 1) packetizer->priv->iconvs[_ICONV_ISO8859_1] = g_iconv_open ("utf-8", iconvtablename[_ICONV_ISO8859_1]); iconv = packetizer->priv->iconvs[_ICONV_ISO8859_1]; GST_INFO ("Trying encoding ISO 8859-1"); converted_str = convert_to_utf8 (text, length, 1, iconv, FALSE, &error); if (error != NULL) { GST_WARNING ("Could not convert string while assuming encoding ISO 8859-1: %s", error->message); g_error_free (error); goto failed; } } else if (encoding == _ICONV_ISO6937) { /* The first part of ISO 6937 is identical to ISO 8859-9, but * they differ in the second part. Some channels don't * provide the first byte that indicates ISO 8859-9 encoding. * If decoding from ISO 6937 failed, we try ISO 8859-9 here. */ if (packetizer->priv->iconvs[_ICONV_ISO8859_9] == (GIConv) - 1) packetizer->priv->iconvs[_ICONV_ISO8859_9] = g_iconv_open ("utf-8", iconvtablename[_ICONV_ISO8859_9]); iconv = packetizer->priv->iconvs[_ICONV_ISO8859_9]; GST_INFO ("Trying encoding ISO 8859-9"); converted_str = convert_to_utf8 (text, length, 0, iconv, FALSE, &error); if (error != NULL) { GST_WARNING ("Could not convert string while assuming encoding ISO 8859-9: %s", error->message); g_error_free (error); goto failed; } } else goto failed; } beach: return converted_str; failed: { text += start_text; return g_strndup (text, length - start_text); } } static void mpegts_packetizer_resync (MpegTSPCR * pcr, GstClockTime time, GstClockTime gstpcrtime, gboolean reset_skew) { pcr->base_time = time; pcr->base_pcrtime = gstpcrtime; pcr->prev_out_time = GST_CLOCK_TIME_NONE; pcr->prev_send_diff = GST_CLOCK_TIME_NONE; if (reset_skew) { pcr->window_filling = TRUE; pcr->window_pos = 0; pcr->window_min = 0; pcr->window_size = 0; pcr->skew = 0; } } /* Code mostly copied from -good/gst/rtpmanager/rtpjitterbuffer.c */ /* For the clock skew we use a windowed low point averaging algorithm as can be * found in Fober, Orlarey and Letz, 2005, "Real Time Clock Skew Estimation * over Network Delays": * http://www.grame.fr/Ressources/pub/TR-050601.pdf * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.102.1546 * * The idea is that the jitter is composed of: * * J = N + n * * N : a constant network delay. * n : random added noise. The noise is concentrated around 0 * * In the receiver we can track the elapsed time at the sender with: * * send_diff(i) = (Tsi - Ts0); * * Tsi : The time at the sender at packet i * Ts0 : The time at the sender at the first packet * * This is the difference between the RTP timestamp in the first received packet * and the current packet. * * At the receiver we have to deal with the jitter introduced by the network. * * recv_diff(i) = (Tri - Tr0) * * Tri : The time at the receiver at packet i * Tr0 : The time at the receiver at the first packet * * Both of these values contain a jitter Ji, a jitter for packet i, so we can * write: * * recv_diff(i) = (Cri + D + ni) - (Cr0 + D + n0)) * * Cri : The time of the clock at the receiver for packet i * D + ni : The jitter when receiving packet i * * We see that the network delay is irrelevant here as we can elliminate D: * * recv_diff(i) = (Cri + ni) - (Cr0 + n0)) * * The drift is now expressed as: * * Drift(i) = recv_diff(i) - send_diff(i); * * We now keep the W latest values of Drift and find the minimum (this is the * one with the lowest network jitter and thus the one which is least affected * by it). We average this lowest value to smooth out the resulting network skew. * * Both the window and the weighting used for averaging influence the accuracy * of the drift estimation. Finding the correct parameters turns out to be a * compromise between accuracy and inertia. * * We use a 2 second window or up to 512 data points, which is statistically big * enough to catch spikes (FIXME, detect spikes). * We also use a rather large weighting factor (125) to smoothly adapt. During * startup, when filling the window, we use a parabolic weighting factor, the * more the window is filled, the faster we move to the detected possible skew. * * Returns: @time adjusted with the clock skew. */ static GstClockTime calculate_skew (MpegTSPCR * pcr, guint64 pcrtime, GstClockTime time) { guint64 send_diff, recv_diff; gint64 delta; gint64 old; gint pos, i; GstClockTime gstpcrtime, out_time; guint64 slope; gstpcrtime = PCRTIME_TO_GSTTIME (pcrtime) + pcr->pcroffset; /* first time, lock on to time and gstpcrtime */ if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (pcr->base_time))) { pcr->base_time = time; pcr->prev_out_time = GST_CLOCK_TIME_NONE; GST_DEBUG ("Taking new base time %" GST_TIME_FORMAT, GST_TIME_ARGS (time)); } if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (pcr->base_pcrtime))) { pcr->base_pcrtime = gstpcrtime; pcr->prev_send_diff = -1; GST_DEBUG ("Taking new base pcrtime %" GST_TIME_FORMAT, GST_TIME_ARGS (gstpcrtime)); } /* Handle PCR wraparound and resets */ if (GST_CLOCK_TIME_IS_VALID (pcr->last_pcrtime) && gstpcrtime < pcr->last_pcrtime) { if (pcr->last_pcrtime - gstpcrtime > PCR_GST_MAX_VALUE / 2) { /* PCR wraparound */ GST_DEBUG ("PCR wrap"); pcr->pcroffset += PCR_GST_MAX_VALUE; gstpcrtime = PCRTIME_TO_GSTTIME (pcrtime) + pcr->pcroffset; send_diff = gstpcrtime - pcr->base_pcrtime; } else if (GST_CLOCK_TIME_IS_VALID (time) && pcr->last_pcrtime - gstpcrtime > 15 * GST_SECOND) { /* Assume a reset */ GST_DEBUG ("PCR reset"); /* Calculate PCR we would have expected for the given input time, * essentially applying the reverse correction process * * We want to find the PCR offset to apply * pcroffset = (corrected) gstpcrtime - (received) gstpcrtime * * send_diff = (corrected) gstpcrtime - pcr->base_pcrtime * recv_diff = time - pcr->base_time * out_time = pcr->base_time + send_diff * * We are assuming that send_diff == recv_diff * (corrected) gstpcrtime - pcr->base_pcrtime = time - pcr->base_time * Giving us: * (corrected) gstpcrtime = time - pcr->base_time + pcr->base_pcrtime * * And therefore: * pcroffset = time - pcr->base_time + pcr->base_pcrtime - (received) gstpcrtime **/ pcr->pcroffset += time - pcr->base_time + pcr->base_pcrtime - gstpcrtime; gstpcrtime = PCRTIME_TO_GSTTIME (pcrtime) + pcr->pcroffset; send_diff = gstpcrtime - pcr->base_pcrtime; GST_DEBUG ("Introduced offset is now %" GST_TIME_FORMAT " corrected pcr time %" GST_TIME_FORMAT, GST_TIME_ARGS (pcr->pcroffset), GST_TIME_ARGS (gstpcrtime)); } else { GST_WARNING ("backward timestamps at server but no timestamps"); send_diff = 0; /* at least try to get a new timestamp.. */ pcr->base_time = GST_CLOCK_TIME_NONE; } } else send_diff = gstpcrtime - pcr->base_pcrtime; GST_DEBUG ("gstpcr %" GST_TIME_FORMAT ", buftime %" GST_TIME_FORMAT ", base %" GST_TIME_FORMAT ", send_diff %" GST_TIME_FORMAT, GST_TIME_ARGS (gstpcrtime), GST_TIME_ARGS (time), GST_TIME_ARGS (pcr->base_pcrtime), GST_TIME_ARGS (send_diff)); /* keep track of the last extended pcrtime */ pcr->last_pcrtime = gstpcrtime; /* we don't have an arrival timestamp so we can't do skew detection. we * should still apply a timestamp based on RTP timestamp and base_time */ if (!GST_CLOCK_TIME_IS_VALID (time) || !GST_CLOCK_TIME_IS_VALID (pcr->base_time)) goto no_skew; /* elapsed time at receiver, includes the jitter */ recv_diff = time - pcr->base_time; /* Ignore packets received at 100% the same time (i.e. from the same input buffer) */ if (G_UNLIKELY (time == pcr->prev_in_time && GST_CLOCK_TIME_IS_VALID (pcr->prev_in_time))) goto no_skew; /* measure the diff */ delta = ((gint64) recv_diff) - ((gint64) send_diff); /* measure the slope, this gives a rought estimate between the sender speed * and the receiver speed. This should be approximately 8, higher values * indicate a burst (especially when the connection starts) */ slope = recv_diff > 0 ? (send_diff * 8) / recv_diff : 8; GST_DEBUG ("time %" GST_TIME_FORMAT ", base %" GST_TIME_FORMAT ", recv_diff %" GST_TIME_FORMAT ", slope %" G_GUINT64_FORMAT, GST_TIME_ARGS (time), GST_TIME_ARGS (pcr->base_time), GST_TIME_ARGS (recv_diff), slope); /* if the difference between the sender timeline and the receiver timeline * changed too quickly we have to resync because the server likely restarted * its timestamps. */ if (ABS (delta - pcr->skew) > GST_SECOND) { GST_WARNING ("delta - skew: %" GST_TIME_FORMAT " too big, reset skew", GST_TIME_ARGS (delta - pcr->skew)); mpegts_packetizer_resync (pcr, time, gstpcrtime, TRUE); send_diff = 0; delta = 0; } pos = pcr->window_pos; if (G_UNLIKELY (pcr->window_filling)) { /* we are filling the window */ GST_DEBUG ("filling %d, delta %" G_GINT64_FORMAT, pos, delta); pcr->window[pos++] = delta; /* calc the min delta we observed */ if (G_UNLIKELY (pos == 1 || delta < pcr->window_min)) pcr->window_min = delta; if (G_UNLIKELY (send_diff >= MAX_TIME || pos >= MAX_WINDOW)) { pcr->window_size = pos; /* window filled */ GST_DEBUG ("min %" G_GINT64_FORMAT, pcr->window_min); /* the skew is now the min */ pcr->skew = pcr->window_min; pcr->window_filling = FALSE; } else { gint perc_time, perc_window, perc; /* figure out how much we filled the window, this depends on the amount of * time we have or the max number of points we keep. */ perc_time = send_diff * 100 / MAX_TIME; perc_window = pos * 100 / MAX_WINDOW; perc = MAX (perc_time, perc_window); /* make a parabolic function, the closer we get to the MAX, the more value * we give to the scaling factor of the new value */ perc = perc * perc; /* quickly go to the min value when we are filling up, slowly when we are * just starting because we're not sure it's a good value yet. */ pcr->skew = (perc * pcr->window_min + ((10000 - perc) * pcr->skew)) / 10000; pcr->window_size = pos + 1; } } else { /* pick old value and store new value. We keep the previous value in order * to quickly check if the min of the window changed */ old = pcr->window[pos]; pcr->window[pos++] = delta; if (G_UNLIKELY (delta <= pcr->window_min)) { /* if the new value we inserted is smaller or equal to the current min, * it becomes the new min */ pcr->window_min = delta; } else if (G_UNLIKELY (old == pcr->window_min)) { gint64 min = G_MAXINT64; /* if we removed the old min, we have to find a new min */ for (i = 0; i < pcr->window_size; i++) { /* we found another value equal to the old min, we can stop searching now */ if (pcr->window[i] == old) { min = old; break; } if (pcr->window[i] < min) min = pcr->window[i]; } pcr->window_min = min; } /* average the min values */ pcr->skew = (pcr->window_min + (124 * pcr->skew)) / 125; GST_DEBUG ("delta %" G_GINT64_FORMAT ", new min: %" G_GINT64_FORMAT, delta, pcr->window_min); } /* wrap around in the window */ if (G_UNLIKELY (pos >= pcr->window_size)) pos = 0; pcr->window_pos = pos; no_skew: /* the output time is defined as the base timestamp plus the PCR time * adjusted for the clock skew .*/ if (pcr->base_time != -1) { out_time = pcr->base_time + send_diff; /* skew can be negative and we don't want to make invalid timestamps */ if (pcr->skew < 0 && out_time < -pcr->skew) { out_time = 0; } else { out_time += pcr->skew; } /* check if timestamps are not going backwards, we can only check this if we * have a previous out time and a previous send_diff */ if (G_LIKELY (pcr->prev_out_time != -1 && pcr->prev_send_diff != -1)) { /* now check for backwards timestamps */ if (G_UNLIKELY ( /* if the server timestamps went up and the out_time backwards */ (send_diff > pcr->prev_send_diff && out_time < pcr->prev_out_time) || /* if the server timestamps went backwards and the out_time forwards */ (send_diff < pcr->prev_send_diff && out_time > pcr->prev_out_time) || /* if the server timestamps did not change */ send_diff == pcr->prev_send_diff)) { GST_DEBUG ("backwards timestamps, using previous time"); out_time = GSTTIME_TO_MPEGTIME (out_time); } } } else { /* We simply use the pcrtime without applying any skew compensation */ out_time = time; } pcr->prev_out_time = out_time; pcr->prev_in_time = time; pcr->prev_send_diff = send_diff; GST_DEBUG ("skew %" G_GINT64_FORMAT ", out %" GST_TIME_FORMAT, pcr->skew, GST_TIME_ARGS (out_time)); return out_time; } static void record_pcr (MpegTSPacketizer2 * packetizer, MpegTSPCR * pcrtable, guint64 pcr, guint64 offset) { MpegTSPacketizerPrivate *priv = packetizer->priv; /* Check against first PCR */ if (pcrtable->first_pcr == -1 || pcrtable->first_offset > offset) { GST_DEBUG ("Recording first value. PCR:%" G_GUINT64_FORMAT " offset:%" G_GUINT64_FORMAT " pcr_pid:0x%04x", pcr, offset, pcrtable->pid); pcrtable->first_pcr = pcr; pcrtable->first_pcr_ts = PCRTIME_TO_GSTTIME (pcr); pcrtable->first_offset = offset; priv->nb_seen_offsets++; } else /* If we didn't update the first PCR, let's check against last PCR */ if (pcrtable->last_pcr == -1 || pcrtable->last_offset < offset) { GST_DEBUG ("Recording last value. PCR:%" G_GUINT64_FORMAT " offset:%" G_GUINT64_FORMAT " pcr_pid:0x%04x", pcr, offset, pcrtable->pid); if (G_UNLIKELY (pcrtable->first_pcr != -1 && pcr < pcrtable->first_pcr)) { GST_DEBUG ("rollover detected"); pcr += PCR_MAX_VALUE; } pcrtable->last_pcr = pcr; pcrtable->last_pcr_ts = PCRTIME_TO_GSTTIME (pcr); pcrtable->last_offset = offset; priv->nb_seen_offsets++; } } guint mpegts_packetizer_get_seen_pcr (MpegTSPacketizer2 * packetizer) { return packetizer->priv->nb_seen_offsets; } GstClockTime mpegts_packetizer_offset_to_ts (MpegTSPacketizer2 * packetizer, guint64 offset, guint16 pid) { MpegTSPacketizerPrivate *priv = packetizer->priv; MpegTSPCR *pcrtable; GstClockTime res; if (G_UNLIKELY (!packetizer->calculate_offset)) return GST_CLOCK_TIME_NONE; if (G_UNLIKELY (priv->refoffset == -1)) return GST_CLOCK_TIME_NONE; if (G_UNLIKELY (offset < priv->refoffset)) return GST_CLOCK_TIME_NONE; pcrtable = get_pcr_table (packetizer, pid); if (G_UNLIKELY (pcrtable->last_offset <= pcrtable->first_offset)) return GST_CLOCK_TIME_NONE; /* Convert byte difference into time difference */ res = PCRTIME_TO_GSTTIME (gst_util_uint64_scale (offset - priv->refoffset, pcrtable->last_pcr - pcrtable->first_pcr, pcrtable->last_offset - pcrtable->first_offset)); GST_DEBUG ("Returning timestamp %" GST_TIME_FORMAT " for offset %" G_GUINT64_FORMAT, GST_TIME_ARGS (res), offset); return res; } GstClockTime mpegts_packetizer_pts_to_ts (MpegTSPacketizer2 * packetizer, GstClockTime pts, guint16 pcr_pid) { GstClockTime res = GST_CLOCK_TIME_NONE; MpegTSPCR *pcrtable = get_pcr_table (packetizer, pcr_pid); /* Use clock skew if present */ if (packetizer->calculate_skew && GST_CLOCK_TIME_IS_VALID (pcrtable->base_time)) { GST_DEBUG ("pts %" G_GUINT64_FORMAT " base_pcrtime:%" G_GUINT64_FORMAT " base_time:%" GST_TIME_FORMAT, pts, pcrtable->base_pcrtime, GST_TIME_ARGS (pcrtable->base_time)); res = pts + pcrtable->pcroffset - pcrtable->base_pcrtime + pcrtable->base_time + pcrtable->skew; } else /* If not, use pcr observations */ if (packetizer->calculate_offset && pcrtable->first_pcr != -1) { /* Rollover */ if (G_UNLIKELY (pts < pcrtable->first_pcr_ts)) pts += MPEGTIME_TO_GSTTIME (PTS_DTS_MAX_VALUE); res = pts - pcrtable->first_pcr_ts; } else GST_WARNING ("Not enough information to calculate proper timestamp"); GST_DEBUG ("Returning timestamp %" GST_TIME_FORMAT " for pts %" GST_TIME_FORMAT " pcr_pid:0x%04x", GST_TIME_ARGS (res), GST_TIME_ARGS (pts), pcr_pid); return res; } guint64 mpegts_packetizer_ts_to_offset (MpegTSPacketizer2 * packetizer, GstClockTime ts, guint16 pcr_pid) { MpegTSPacketizerPrivate *priv = packetizer->priv; MpegTSPCR *pcrtable; guint64 res; if (!packetizer->calculate_offset) return -1; pcrtable = get_pcr_table (packetizer, pcr_pid); if (pcrtable->first_pcr == -1) return -1; GST_DEBUG ("ts(pcr) %" G_GUINT64_FORMAT " first_pcr:%" G_GUINT64_FORMAT, GSTTIME_TO_MPEGTIME (ts), pcrtable->first_pcr); /* Convert ts to PCRTIME */ res = gst_util_uint64_scale (GSTTIME_TO_PCRTIME (ts), pcrtable->last_offset - pcrtable->first_offset, pcrtable->last_pcr - pcrtable->first_pcr); res += pcrtable->first_offset + priv->refoffset; GST_DEBUG ("Returning offset %" G_GUINT64_FORMAT " for ts %" GST_TIME_FORMAT, res, GST_TIME_ARGS (ts)); return res; } void mpegts_packetizer_set_reference_offset (MpegTSPacketizer2 * packetizer, guint64 refoffset) { GST_DEBUG ("Setting reference offset to %" G_GUINT64_FORMAT, refoffset); packetizer->priv->refoffset = refoffset; }