]> git.sesse.net Git - decode_sdt/commitdiff
Initial commit.
authorSteinar H. Gunderson <sesse@samfundet.no>
Sun, 29 Sep 2013 17:04:20 +0000 (19:04 +0200)
committerSteinar H. Gunderson <sesse@samfundet.no>
Sun, 29 Sep 2013 17:04:20 +0000 (19:04 +0200)
decode_sdt.c [new file with mode: 0644]

diff --git a/decode_sdt.c b/decode_sdt.c
new file mode 100644 (file)
index 0000000..7f336f8
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * Decode SDT and NIT from a TS stream (for instance dvr0), and output in a
+ * relatively machine-readable format.
+ *
+ * Based on the decode_sdt.c example from libdvbpsi, and uses some source code
+ * from MuMuDVB.
+ *
+ * Copyright (C):
+ *    2001-2011 VideoLAN
+ *    Dave Chapman <dave@dchapman.com> 2001, 2002.
+ *    2004-2011 Brice DUBOST 
+ *    2013 Steinar H. Gunderson
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <iconv.h>
+
+#include <stdint.h>
+
+#include <dvbpsi/dvbpsi.h>
+#include <dvbpsi/psi.h>
+#include <dvbpsi/demux.h>
+#include <dvbpsi/descriptor.h>
+#include <dvbpsi/sdt.h>
+#include <dvbpsi/nit.h>
+
+static int packet_num = 0;
+static int last_used_packet_num = 0;
+
+/**
+@brief The different encodings that can be used
+Cf EN 300 468 Annex A (I used v1.9.1)
+ */
+static const char *encodings_en300468[] ={
+       "ISO8859-1",
+       "ISO8859-2",
+       "ISO8859-3",
+       "ISO8859-4",
+       "ISO8859-5",
+       "ISO8859-6",
+       "ISO8859-7",
+       "ISO8859-8",
+       "ISO8859-9",
+       "ISO8859-10",
+       "ISO8859-11",
+       "ISO8859-12",
+       "ISO8859-13",
+       "ISO8859-14",
+       "ISO8859-15",
+       "ISO-10646", //control char 0x11
+       "GB2312",    //control char 0x13
+       "BIG5",      //control char 0x14
+       "ISO-10646/UTF8",      //control char 0x15
+};
+
+// Convert text according to EN 300 468 annex A.
+static int convert_en300468_string(char *string, int max_len)
+{
+       int encoding_control_char = 8; //cf encodings_en300468
+       char *dest;
+       char *tempdest, *tempbuf;
+       unsigned char *src;
+       /* remove control characters and convert to UTF-8 the channel name */
+       //If no channel encoding is specified, it seems that most of the broadcasters
+       //uses ISO/IEC 8859-9. But the norm (EN 300 468) said that it should be Latin-1 (ISO/IEC 6937 + euro)
+
+       //temporary buffers allocation
+       tempdest = tempbuf = (char *)malloc(sizeof(char)*2*strlen(string));
+       if (tempdest==NULL) {
+               return -1;
+       }
+
+       int len = 0;
+       for (src = (unsigned char *)string; *src; src++) {
+               if (*src >= 0x20 && (*src < 0x80 || *src > 0x9f)) {
+                       //We copy non-control characters
+                       *tempdest++ = *src;
+                       len++;
+               } else if (*src <= 0x20) {
+                       //control character recognition based on EN 300 468 v1.9.1 Annex A
+                       if (*src <= 0x0b) {
+                               encoding_control_char=(int) *src+4-1;
+                       } else if(*src == 0x10) { //ISO/IEC 8859 : See table A.4
+                               src++;//we skip the current byte
+                               src++;//This one is always set to 0
+                               if (*src >= 0x01 && *src <=0x0f)
+                                       encoding_control_char=(int) *src-1;
+                       }
+                       else if (*src==0x11) // ISO/IEC 10646 : Basic Multilingual Plane
+                               encoding_control_char = 15;
+                       else if (*src==0x13) // GB-2312-1980 : Simplified Chinese Character
+                               encoding_control_char = 16;
+                       else if (*src==0x14) // Big5 subset of ISO/IEC 10646 : Traditional Chinese
+                               encoding_control_char = 17;
+                       else if (*src==0x15) // UTF-8 encoding of ISO/IEC 10646 : Basic Multilingual Plane
+                               encoding_control_char = 18;
+               } 
+       }
+       //Conversion to utf8
+       iconv_t cd;
+       //we open the conversion table
+       cd = iconv_open("UTF8", encodings_en300468[encoding_control_char]);
+
+       size_t inSize, outSize = max_len;
+       inSize = len;
+       //pointers initialisation because iconv change them, we store
+       dest = string;
+       tempdest = tempbuf;
+       //conversion
+       iconv(cd, &tempdest, &inSize, &dest, &outSize);
+       *dest = '\0';
+       free(tempbuf);
+       iconv_close(cd);
+
+       return encoding_control_char;
+}
+
+
+static bool ReadPacket(int i_fd, uint8_t * p_dst)
+{
+       static uint8_t buf[188 * 100];
+       static int in_buf_start = 0;
+       static int in_buf_end = 0;
+       
+has_stuff:
+       // Skip to the first 0x47 byte.
+       while (in_buf_end > in_buf_start && buf[in_buf_start] != 0x47) {
+               ++in_buf_start;
+       }
+
+       if (in_buf_end - in_buf_start >= 188) {
+               memcpy(p_dst, buf + in_buf_start, 188);
+               in_buf_start += 188;
+               return true;
+       }
+
+       // Check if there's a partial packet in the buffer.
+       int partial_len = in_buf_end - in_buf_start;
+       if (partial_len == 0) {
+               in_buf_start = in_buf_end = 0;
+       } else if (partial_len > 0) {
+               memmove(buf, buf + in_buf_start, partial_len);
+               in_buf_start = 0;
+               in_buf_end = partial_len;
+       }
+
+       int ret = read(i_fd, buf + in_buf_start, sizeof(buf) - in_buf_end);
+       if (ret == -1) {
+               perror("read");
+               return false;
+       }
+       if (ret == 0) {
+               return false;
+       }
+
+       in_buf_end += ret;
+
+       goto has_stuff;
+}
+
+
+static void ParseSDT(void *p_zero, dvbpsi_sdt_t * p_sdt)
+{
+       last_used_packet_num = packet_num;
+       dvbpsi_sdt_service_t *p_service = p_sdt->p_first_service;
+       for (dvbpsi_sdt_service_t * p_service = p_sdt->p_first_service;
+            p_service != NULL;
+            p_service = p_service->p_next) {
+               int service_type = -1;
+               char provider_name[256] = { 0 };
+               char channel_name[256] = { 0 };
+               dvbpsi_descriptor_t *p_descriptor =
+                   p_service->p_first_descriptor;
+
+               // Search for a Service Descriptor (0x48), which contains the provider and channel names.
+               for (dvbpsi_descriptor_t * p_descriptor = p_service->p_first_descriptor;
+                    p_descriptor != NULL;
+                    p_descriptor = p_descriptor->p_next) {
+                       uint8_t *ptr;
+                       int i, len;
+                       if (p_descriptor->i_tag != 0x48) {
+                               continue;
+                       }
+                       service_type = p_descriptor->p_data[0];
+                       ptr = p_descriptor->p_data + 1;
+
+                       len = *ptr++;
+                       memcpy(provider_name, ptr, len);
+                       provider_name[len] = 0;
+                       ptr += len;
+
+                       len = *ptr++;
+                       memcpy(channel_name, ptr, len);
+                       channel_name[len] = 0;
+
+                       convert_en300468_string(provider_name, sizeof(provider_name));
+                       convert_en300468_string(channel_name, sizeof(channel_name));
+               }
+
+               // Show television types only.
+               if (service_type == 0x01 || service_type == 0x11 || service_type == 0x16 ||
+                   service_type == 0x19 || service_type == 0x1c) {
+                       printf("sid %d/%d: ts_id=%d, provider_name=\"%s\", channel_name=\"%s\"\n",
+                            p_sdt->i_network_id, p_service->i_service_id,
+                            p_sdt->i_extension, provider_name,
+                            channel_name);
+               }
+       }
+       dvbpsi_sdt_delete(p_sdt);
+}
+
+// little endian assumed
+typedef struct {
+       uint8_t frequency_4;
+       uint8_t frequency_3;
+       uint8_t frequency_2;
+       uint8_t frequency_1;
+       uint16_t orbital_position;      // big-endian
+       uint8_t modulation_type : 2;
+       uint8_t modulation_system : 1;
+       uint8_t roll_off : 2;
+       uint8_t polarization : 2;
+       uint8_t west_east_flag : 1;
+       uint8_t symbol_rate_12;
+       uint8_t symbol_rate_34;
+       uint8_t symbol_rate_56;
+       uint8_t FEC_inner : 4;
+       uint8_t symbol_rate_7 : 4;
+} descr_sat_delivery_t;
+
+static void ParseNIT(void *p_zero, dvbpsi_nit_t * p_nit)
+{
+       last_used_packet_num = packet_num;
+       for (dvbpsi_nit_ts_t *p_ts = p_nit->p_first_ts;
+            p_ts != NULL;
+            p_ts = p_ts->p_next) {
+               // Search through the descriptors until we find one of type 0x43
+               // (Delivery System Descriptor).
+               for (dvbpsi_descriptor_t *p_descriptor = p_ts->p_first_descriptor;
+                    p_descriptor != NULL;
+                    p_descriptor = p_descriptor->p_next) {
+                       char buf[256], *ptr;
+                       int i, len;
+                       if (p_descriptor->i_tag != 0x43) {
+                               continue;
+                       }
+
+                       descr_sat_delivery_t *d =
+                           (descr_sat_delivery_t *) p_descriptor->p_data;
+                       printf("ts %d/%d: ", p_nit->i_network_id, p_ts->i_ts_id);
+                       printf("freq=%x%02x%02x, ", d->frequency_4,
+                              d->frequency_3, d->frequency_2,
+                              d->frequency_1);
+                       printf("pol=%c, ", "HVLR"[d->polarization]);
+                       if (d->modulation_system) {
+                               printf("delivery_system=DVBS2, ");
+                       } else {
+                               printf("delivery_system=DVBS, ");
+                       }
+                       if (d->modulation_type == 0) {
+                               printf("modulation=QAMAUTO, ");
+                       } else if (d->modulation_type == 1) {
+                               printf("modulation=QPSK, ");
+                       } else if (d->modulation_type == 2) {
+                               printf("modulation=8PSK, ");
+                       } else if (d->modulation_type == 3) {
+                               printf("modulation=QAM64, ");
+                       }
+                       printf("srate=%x%02x%02x, ", d->symbol_rate_12, d->symbol_rate_34, d->symbol_rate_56);
+                       if (d->FEC_inner == 0) {
+                               printf("coderate=auto");
+                       } else if (d->FEC_inner == 1) {
+                               printf("coderate=1/2");
+                       } else if (d->FEC_inner == 2) {
+                               printf("coderate=2/3");
+                       } else if (d->FEC_inner == 3) {
+                               printf("coderate=3/4");
+                       } else if (d->FEC_inner == 4) {
+                               printf("coderate=5/6");
+                       } else if (d->FEC_inner == 5) {
+                               printf("coderate=7/8");
+                       } else if (d->FEC_inner == 6) {
+                               printf("coderate=8/9");
+                       } else if (d->FEC_inner == 7) {
+                               printf("coderate=3/5");
+                       } else if (d->FEC_inner == 8) {
+                               printf("coderate=4/5");
+                       } else if (d->FEC_inner == 9) {
+                               printf("coderate=9/10");
+                       } else {
+                               printf("coderate=none");
+                       }
+                       printf("\n");
+                       break;
+               }
+       }
+       dvbpsi_nit_delete(p_nit);
+}
+
+/*****************************************************************************
+ * DVBPSI messaging callback
+ *****************************************************************************/
+static void message(dvbpsi_t * handle, const dvbpsi_msg_level_t level,
+                   const char *msg)
+{
+       switch (level) {
+       case DVBPSI_MSG_ERROR:
+               fprintf(stderr, "Error: ");
+               break;
+       case DVBPSI_MSG_WARN:
+               fprintf(stderr, "Warning: ");
+               break;
+       case DVBPSI_MSG_DEBUG:
+               fprintf(stderr, "Debug: ");
+               break;
+       default:                /* do nothing */
+               return;
+       }
+       fprintf(stderr, "%s\n", msg);
+}
+
+/*****************************************************************************
+ * NewSubtable
+ *****************************************************************************/
+static void NewSubtableNIT(dvbpsi_t * p_dvbpsi, uint8_t i_table_id,
+                          uint16_t i_extension, void *p_zero)
+{
+       if (i_table_id == 0x40) {
+               if (!dvbpsi_nit_attach(p_dvbpsi, i_table_id, i_extension, ParseNIT, NULL))
+                       fprintf(stderr, "Failed to attach PSI subdecoder\n");
+       }
+}
+
+/*****************************************************************************
+ * NewSubtable
+ *****************************************************************************/
+static void NewSubtableSDT(dvbpsi_t * p_dvbpsi, uint8_t i_table_id,
+                          uint16_t i_extension, void *p_zero)
+{
+       if (i_table_id == 0x42 || i_table_id == 0x46) {
+               if (!dvbpsi_sdt_attach(p_dvbpsi, i_table_id, i_extension, ParseSDT, NULL))
+                       fprintf(stderr, "Failed to attach SDT subdecoder\n");
+       }
+}
+
+/*****************************************************************************
+ * main
+ *****************************************************************************/
+int main(int i_argc, char *pa_argv[])
+{
+       int i_fd;
+       uint8_t data[188];
+       dvbpsi_t *p_dvbpsi_sdt, *p_dvbpsi_nit;
+       bool b_ok;
+
+       if (i_argc != 2)
+               return 1;
+
+       i_fd = open(pa_argv[1], 0);
+       if (i_fd < 0)
+               return 1;
+
+       p_dvbpsi_sdt = dvbpsi_new(&message, DVBPSI_MSG_ERROR);
+       if (p_dvbpsi_sdt == NULL)
+               goto out;
+
+       p_dvbpsi_nit = dvbpsi_new(&message, DVBPSI_MSG_ERROR);
+       if (p_dvbpsi_nit == NULL)
+               goto out;
+
+       if (!dvbpsi_AttachDemux(p_dvbpsi_sdt, NewSubtableSDT, NULL))
+               goto out;
+
+       if (!dvbpsi_AttachDemux(p_dvbpsi_nit, NewSubtableNIT, NULL))
+               goto out;
+
+       while (ReadPacket(i_fd, data)) {
+               uint16_t i_pid = ((uint16_t)(data[1] & 0x1f) << 8) + data[2];
+               if (i_pid == 0x10)
+                       dvbpsi_packet_push(p_dvbpsi_nit, data);
+               if (i_pid == 0x11)
+                       dvbpsi_packet_push(p_dvbpsi_sdt, data);
+
+               // After 100k packets with no new information, we assume we are done.
+               ++packet_num;
+               if ((packet_num - last_used_packet_num) >= 100000)
+                       break;
+       }
+
+out:
+       if (p_dvbpsi_sdt) {
+               dvbpsi_DetachDemux(p_dvbpsi_sdt);
+               dvbpsi_delete(p_dvbpsi_sdt);
+       }
+       if (p_dvbpsi_nit) {
+               dvbpsi_DetachDemux(p_dvbpsi_nit);
+               dvbpsi_delete(p_dvbpsi_nit);
+       }
+       close(i_fd);
+       return 0;
+}