/* GStreamer * * ts-parser.c: sample application to display mpeg-ts info from any pipeline * Copyright (C) 2013 * Edward Hervey * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define DUMP_DESCRIPTORS 0 #include #include #include #include #include static void gst_info_dump_mem_line (gchar * linebuf, gsize linebuf_size, const guint8 * mem, gsize mem_offset, gsize mem_size) { gchar hexstr[50], ascstr[18], digitstr[4]; if (mem_size > 16) mem_size = 16; hexstr[0] = '\0'; ascstr[0] = '\0'; if (mem != NULL) { guint i = 0; mem += mem_offset; while (i < mem_size) { ascstr[i] = (g_ascii_isprint (mem[i])) ? mem[i] : '.'; g_snprintf (digitstr, sizeof (digitstr), "%02x ", mem[i]); g_strlcat (hexstr, digitstr, sizeof (hexstr)); ++i; } ascstr[i] = '\0'; } g_snprintf (linebuf, linebuf_size, "%08x: %-48.48s %-16.16s", (guint) mem_offset, hexstr, ascstr); } static void dump_memory_content (GstMpegTsDescriptor * desc, guint spacing) { gsize off = 0; while (off < desc->length) { gchar buf[128]; /* gst_info_dump_mem_line will process 16 bytes at most */ gst_info_dump_mem_line (buf, sizeof (buf), desc->data, off + 2, desc->length - off); g_printf ("%*s %s\n", spacing, "", buf); off += 16; } } static const gchar * descriptor_name (gint val) { GEnumValue *en; en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_DESCRIPTOR_TYPE)), val); if (en == NULL) /* Else try with DVB enum types */ en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_DVB_DESCRIPTOR_TYPE)), val); if (en == NULL) /* Else try with ATSC enum types */ en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_ATSC_DESCRIPTOR_TYPE)), val); if (en == NULL) /* Else try with ISB enum types */ en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_ISDB_DESCRIPTOR_TYPE)), val); if (en == NULL) /* Else try with misc enum types */ en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_MISC_DESCRIPTOR_TYPE)), val); if (en == NULL) return "UNKNOWN/PRIVATE"; return en->value_nick; } static const gchar * table_id_name (gint val) { GEnumValue *en; en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_SECTION_TABLE_ID)), val); if (en == NULL) /* Else try with DVB enum types */ en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_SECTION_DVB_TABLE_ID)), val); if (en == NULL) /* Else try with ATSC enum types */ en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_SECTION_ATSC_TABLE_ID)), val); if (en == NULL) /* Else try with SCTE enum types */ en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (GST_TYPE_MPEG_TS_SECTION_SCTE_TABLE_ID)), val); if (en == NULL) return "UNKNOWN/PRIVATE"; return en->value_nick; } static const gchar * enum_name (GType instance_type, gint val) { GEnumValue *en; en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek (instance_type)), val); if (!en) return "UNKNOWN/PRIVATE"; return en->value_nick; } static void dump_cable_delivery_descriptor (GstMpegTsDescriptor * desc, guint spacing) { GstMpegTsCableDeliverySystemDescriptor res; if (gst_mpegts_descriptor_parse_cable_delivery_system (desc, &res)) { g_printf ("%*s Cable Delivery Descriptor\n", spacing, ""); g_printf ("%*s Frequency : %d Hz\n", spacing, "", res.frequency); g_printf ("%*s Outer FEC : %d\n", spacing, "", res.outer_fec); g_printf ("%*s modulation : %d\n", spacing, "", res.modulation); g_printf ("%*s Symbol rate : %d sym/s\n", spacing, "", res.symbol_rate); g_printf ("%*s Inner FEC : %d\n", spacing, "", res.fec_inner); } } static void dump_dvb_service_list (GstMpegTsDescriptor * desc, guint spacing) { GPtrArray *res; if (gst_mpegts_descriptor_parse_dvb_service_list (desc, &res)) { guint i; g_printf ("%*s DVB Service List Descriptor\n", spacing, ""); for (i = 0; i < res->len; i++) { GstMpegTsDVBServiceListItem *item = g_ptr_array_index (res, i); g_printf ("%*s Service #%d, id:0x%04x, type:0x%x (%s)\n", spacing, "", i, item->service_id, item->type, enum_name (GST_TYPE_MPEG_TS_DVB_SERVICE_TYPE, item->type)); } g_ptr_array_unref (res); } } static void dump_logical_channel_descriptor (GstMpegTsDescriptor * desc, guint spacing) { GstMpegTsLogicalChannelDescriptor res; guint i; if (gst_mpegts_descriptor_parse_logical_channel (desc, &res)) { g_printf ("%*s Logical Channel Descriptor (%d channels)\n", spacing, "", res.nb_channels); for (i = 0; i < res.nb_channels; i++) { GstMpegTsLogicalChannel *chann = &res.channels[i]; g_printf ("%*s service_id: 0x%04x, logical channel number:%4d\n", spacing, "", chann->service_id, chann->logical_channel_number); } } } static void dump_iso_639_language (GstMpegTsDescriptor * desc, guint spacing) { guint i; GstMpegTsISO639LanguageDescriptor res; if (gst_mpegts_descriptor_parse_iso_639_language (desc, &res)) { for (i = 0; i < res.nb_language; i++) g_print ("%*s ISO 639 Language Descriptor %c%c%c , audio_type:0x%x (%s)\n", spacing, "", res.language[i][0], res.language[i][1], res.language[i][2], res.audio_type[i], enum_name (GST_TYPE_MPEG_TS_ISO639_AUDIO_TYPE, res.audio_type[i])); } } static void dump_descriptors (GPtrArray * descriptors, guint spacing) { guint i; for (i = 0; i < descriptors->len; i++) { GstMpegTsDescriptor *desc = g_ptr_array_index (descriptors, i); g_printf ("%*s [descriptor 0x%02x (%s) length:%d]\n", spacing, "", desc->tag, descriptor_name (desc->tag), desc->length); if (DUMP_DESCRIPTORS) dump_memory_content (desc, spacing + 2); switch (desc->tag) { case GST_MTS_DESC_REGISTRATION: { const guint8 *data = desc->data + 2; #define SAFE_CHAR(a) (g_ascii_isprint(a) ? a : '.') g_printf ("%*s Registration : %c%c%c%c [%02x%02x%02x%02x]\n", spacing, "", SAFE_CHAR (data[0]), SAFE_CHAR (data[1]), SAFE_CHAR (data[2]), SAFE_CHAR (data[3]), data[0], data[1], data[2], data[3]); break; } case GST_MTS_DESC_DVB_NETWORK_NAME: { gchar *network_name; if (gst_mpegts_descriptor_parse_dvb_network_name (desc, &network_name)) { g_printf ("%*s Network Name : %s\n", spacing, "", network_name); g_free (network_name); } break; } case GST_MTS_DESC_DVB_SERVICE_LIST: { dump_dvb_service_list (desc, spacing + 2); break; } case GST_MTS_DESC_DVB_CABLE_DELIVERY_SYSTEM: dump_cable_delivery_descriptor (desc, spacing + 2); break; case GST_MTS_DESC_DTG_LOGICAL_CHANNEL: dump_logical_channel_descriptor (desc, spacing + 2); break; case GST_MTS_DESC_DVB_SERVICE: { gchar *service_name, *provider_name; GstMpegTsDVBServiceType service_type; if (gst_mpegts_descriptor_parse_dvb_service (desc, &service_type, &service_name, &provider_name)) { g_printf ("%*s Service Descriptor, type:0x%02x (%s)\n", spacing, "", service_type, enum_name (GST_TYPE_MPEG_TS_DVB_SERVICE_TYPE, service_type)); g_printf ("%*s service_name : %s\n", spacing, "", service_name); g_printf ("%*s provider_name : %s\n", spacing, "", provider_name); g_free (service_name); g_free (provider_name); } break; } case GST_MTS_DESC_ISO_639_LANGUAGE: dump_iso_639_language (desc, spacing + 2); break; case GST_MTS_DESC_DVB_SHORT_EVENT: { gchar *language_code, *event_name, *text; if (gst_mpegts_descriptor_parse_dvb_short_event (desc, &language_code, &event_name, &text)) { g_printf ("%*s Short Event, language_code:%s\n", spacing, "", language_code); g_printf ("%*s event_name : %s\n", spacing, "", event_name); g_printf ("%*s text : %s\n", spacing, "", text); g_free (language_code); g_free (event_name); g_free (text); } } break; case GST_MTS_DESC_DVB_SUBTITLING: { gchar lang[4]; guint8 type; guint16 composition; guint16 ancillary; guint i; for (i = 0; gst_mpegts_descriptor_parse_dvb_subtitling_idx (desc, i, &lang, &type, &composition, &ancillary); i++) { g_printf ("%*s Subtitling, language_code:%s\n", spacing, "", lang); g_printf ("%*s type : %u\n", spacing, "", type); g_printf ("%*s composition page id : %u\n", spacing, "", composition); g_printf ("%*s ancillary page id : %u\n", spacing, "", ancillary); } } break; case GST_MTS_DESC_DVB_TELETEXT: { GstMpegTsDVBTeletextType type; gchar lang[4]; guint8 magazine, page_number; guint i; for (i = 0; gst_mpegts_descriptor_parse_dvb_teletext_idx (desc, i, &lang, &type, &magazine, &page_number); i++) { g_printf ("%*s Teletext, type:0x%02x (%s)\n", spacing, "", type, enum_name (GST_TYPE_MPEG_TS_DVB_TELETEXT_TYPE, type)); g_printf ("%*s language : %s\n", spacing, "", lang); g_printf ("%*s magazine : %u\n", spacing, "", magazine); g_printf ("%*s page number : %u\n", spacing, "", page_number); } } break; default: break; } } } static void dump_pat (GstMpegTsSection * section) { GPtrArray *pat = gst_mpegts_section_get_pat (section); guint i, len; len = pat->len; g_printf (" %d program(s):\n", len); for (i = 0; i < len; i++) { GstMpegTsPatProgram *patp = g_ptr_array_index (pat, i); g_print (" program_number:%6d (0x%04x), network_or_program_map_PID:0x%04x\n", patp->program_number, patp->program_number, patp->network_or_program_map_PID); } g_ptr_array_unref (pat); } static void dump_pmt (GstMpegTsSection * section) { const GstMpegTsPMT *pmt = gst_mpegts_section_get_pmt (section); guint i, len; g_printf (" program_number : 0x%04x\n", section->subtable_extension); g_printf (" pcr_pid : 0x%04x\n", pmt->pcr_pid); dump_descriptors (pmt->descriptors, 7); len = pmt->streams->len; g_printf (" %d Streams:\n", len); for (i = 0; i < len; i++) { GstMpegTsPMTStream *stream = g_ptr_array_index (pmt->streams, i); g_printf (" pid:0x%04x , stream_type:0x%02x (%s)\n", stream->pid, stream->stream_type, enum_name (GST_TYPE_MPEG_TS_STREAM_TYPE, stream->stream_type)); dump_descriptors (stream->descriptors, 9); } } static void dump_eit (GstMpegTsSection * section) { const GstMpegTsEIT *eit = gst_mpegts_section_get_eit (section); guint i, len; g_assert (eit); g_printf (" service_id : 0x%04x\n", section->subtable_extension); g_printf (" transport_stream_id : 0x%04x\n", eit->transport_stream_id); g_printf (" original_network_id : 0x%04x\n", eit->original_network_id); g_printf (" segment_last_section_number:0x%02x, last_table_id:0x%02x\n", eit->segment_last_section_number, eit->last_table_id); g_printf (" actual_stream : %s, present_following : %s\n", eit->actual_stream ? "TRUE" : "FALSE", eit->present_following ? "TRUE" : "FALSE"); len = eit->events->len; g_printf (" %d Event(s):\n", len); for (i = 0; i < len; i++) { gchar *tmp = (gchar *) ""; GstMpegTsEITEvent *event = g_ptr_array_index (eit->events, i); if (event->start_time) tmp = gst_date_time_to_iso8601_string (event->start_time); g_printf (" event_id:0x%04x, start_time:%s, duration:%" GST_TIME_FORMAT "\n", event->event_id, tmp, GST_TIME_ARGS (event->duration * GST_SECOND)); g_printf (" running_status:0x%02x (%s), free_CA_mode:%d (%s)\n", event->running_status, enum_name (GST_TYPE_MPEG_TS_RUNNING_STATUS, event->running_status), event->free_CA_mode, event->free_CA_mode ? "MAYBE SCRAMBLED" : "NOT SCRAMBLED"); if (event->start_time) g_free (tmp); dump_descriptors (event->descriptors, 9); } } static void dump_nit (GstMpegTsSection * section) { const GstMpegTsNIT *nit = gst_mpegts_section_get_nit (section); guint i, len; g_assert (nit); g_printf (" network_id : 0x%04x\n", section->subtable_extension); g_printf (" actual_network : %s\n", nit->actual_network ? "TRUE" : "FALSE"); dump_descriptors (nit->descriptors, 7); len = nit->streams->len; g_printf (" %d Streams:\n", len); for (i = 0; i < len; i++) { GstMpegTsNITStream *stream = g_ptr_array_index (nit->streams, i); g_printf (" transport_stream_id:0x%04x , original_network_id:0x%02x\n", stream->transport_stream_id, stream->original_network_id); dump_descriptors (stream->descriptors, 9); } } static void dump_bat (GstMpegTsSection * section) { const GstMpegTsBAT *bat = gst_mpegts_section_get_bat (section); guint i, len; g_assert (bat); g_printf (" bouquet_id : 0x%04x\n", section->subtable_extension); dump_descriptors (bat->descriptors, 7); len = bat->streams->len; g_printf (" %d Streams:\n", len); for (i = 0; i < len; i++) { GstMpegTsBATStream *stream = g_ptr_array_index (bat->streams, i); g_printf (" transport_stream_id:0x%04x , original_network_id:0x%02x\n", stream->transport_stream_id, stream->original_network_id); dump_descriptors (stream->descriptors, 9); } } static void dump_sdt (GstMpegTsSection * section) { const GstMpegTsSDT *sdt = gst_mpegts_section_get_sdt (section); guint i, len; g_assert (sdt); g_printf (" original_network_id : 0x%04x\n", sdt->original_network_id); g_printf (" actual_ts : %s\n", sdt->actual_ts ? "TRUE" : "FALSE"); len = sdt->services->len; g_printf (" %d Services:\n", len); for (i = 0; i < len; i++) { GstMpegTsSDTService *service = g_ptr_array_index (sdt->services, i); g_print (" service_id:0x%04x, EIT_schedule_flag:%d, EIT_present_following_flag:%d\n", service->service_id, service->EIT_schedule_flag, service->EIT_present_following_flag); g_print (" running_status:0x%02x (%s), free_CA_mode:%d (%s)\n", service->running_status, enum_name (GST_TYPE_MPEG_TS_RUNNING_STATUS, service->running_status), service->free_CA_mode, service->free_CA_mode ? "MAYBE SCRAMBLED" : "NOT SCRAMBLED"); dump_descriptors (service->descriptors, 9); } } static void dump_tdt (GstMpegTsSection * section) { GstDateTime *date = gst_mpegts_section_get_tdt (section); if (date) { gchar *str = gst_date_time_to_iso8601_string (date); g_printf (" utc_time : %s\n", str); g_free (str); gst_date_time_unref (date); } else { g_printf (" No utc_time present\n"); } } static void dump_tot (GstMpegTsSection * section) { const GstMpegTsTOT *tot = gst_mpegts_section_get_tot (section); gchar *str = gst_date_time_to_iso8601_string (tot->utc_time); g_printf (" utc_time : %s\n", str); dump_descriptors (tot->descriptors, 7); g_free (str); } static void dump_section (GstMpegTsSection * section) { switch (GST_MPEGTS_SECTION_TYPE (section)) { case GST_MPEGTS_SECTION_PAT: dump_pat (section); break; case GST_MPEGTS_SECTION_PMT: dump_pmt (section); break; case GST_MPEGTS_SECTION_TDT: dump_tdt (section); break; case GST_MPEGTS_SECTION_TOT: dump_tot (section); break; case GST_MPEGTS_SECTION_SDT: dump_sdt (section); break; case GST_MPEGTS_SECTION_NIT: dump_nit (section); break; case GST_MPEGTS_SECTION_BAT: dump_bat (section); break; case GST_MPEGTS_SECTION_EIT: dump_eit (section); break; default: g_printf (" Unknown section type\n"); break; } } static void _on_bus_message (GstBus * bus, GstMessage * message, GMainLoop * mainloop) { /* g_printf ("Got message %s\n", GST_MESSAGE_TYPE_NAME (message)); */ switch (GST_MESSAGE_TYPE (message)) { case GST_MESSAGE_ERROR: case GST_MESSAGE_EOS: g_main_loop_quit (mainloop); break; case GST_MESSAGE_ELEMENT: { GstMpegTsSection *section; if ((section = gst_message_parse_mpegts_section (message))) { const gchar *table_name; table_name = table_id_name (section->table_id); g_print ("Got section: PID:0x%04x type:%s (table_id 0x%02x (%s)) at offset %" G_GUINT64_FORMAT "\n", section->pid, enum_name (GST_TYPE_MPEG_TS_SECTION_TYPE, section->section_type), section->table_id, table_name, section->offset); if (!section->short_section) { g_print (" subtable_extension:0x%04x, version_number:0x%02x\n", section->subtable_extension, section->version_number); g_print (" section_number:0x%02x last_section_number:0x%02x crc:0x%08x\n", section->section_number, section->last_section_number, section->crc); } dump_section (section); g_printf ("\n\n"); gst_mpegts_section_unref (section); } break; } default: break; } } int main (int argc, gchar ** argv) { GstElement *pipeline = NULL; GError *error = NULL; GstBus *bus; GMainLoop *mainloop; gst_init (&argc, &argv); gst_mpegts_initialize (); pipeline = gst_parse_launchv ((const gchar **) &argv[1], &error); if (error) { g_printf ("pipeline could not be constructed: %s\n", error->message); g_error_free (error); return 1; } /* Hack: ensure all enum type classes are loaded */ g_type_class_ref (GST_TYPE_MPEG_TS_SECTION_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_SECTION_TABLE_ID); g_type_class_ref (GST_TYPE_MPEG_TS_RUNNING_STATUS); g_type_class_ref (GST_TYPE_MPEG_TS_DESCRIPTOR_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_DVB_DESCRIPTOR_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_ATSC_DESCRIPTOR_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_ISDB_DESCRIPTOR_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_MISC_DESCRIPTOR_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_ISO639_AUDIO_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_DVB_SERVICE_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_DVB_TELETEXT_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_STREAM_TYPE); g_type_class_ref (GST_TYPE_MPEG_TS_SECTION_DVB_TABLE_ID); g_type_class_ref (GST_TYPE_MPEG_TS_SECTION_ATSC_TABLE_ID); g_type_class_ref (GST_TYPE_MPEG_TS_SECTION_SCTE_TABLE_ID); mainloop = g_main_loop_new (NULL, FALSE); /* Put a bus handler */ bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); gst_bus_add_signal_watch (bus); g_signal_connect (bus, "message", (GCallback) _on_bus_message, mainloop); /* Start pipeline */ gst_element_set_state (pipeline, GST_STATE_PLAYING); g_main_loop_run (mainloop); gst_element_set_state (pipeline, GST_STATE_NULL); gst_object_unref (pipeline); gst_object_unref (bus); return 0; }