/* * Dead Penguin - the set top box * Copyright (c) 2008, 2009 Lee Essen * * This file is part of Dead Penguin. * * Dead Penguin 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 3 of the License, or * (at your option) any later version. * * Dead Penguin 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 Dead Penguin. If not, see . */ #include "core_si.h" #include #include //#include "core_tuner.h" #include "core_tuner2.h" #include "core_tuner_manager.h" #include "core_control.h" #include "dp.h" #include "time.h" #include "libavformat/avformat.h" /* * Table ID's that we care about... */ #define TABLE_ID_PMT 0x02 #define TABLE_ID_DSMCC_UN 0x3b #define TABLE_ID_DSMCC_DOWNLOAD 0x3c #define TABLE_ID_NIT_ACTUAL 0x40 #define TABLE_ID_NIT_OTHER 0x41 #define TABLE_ID_SDT_ACTUAL 0x42 #define TABLE_ID_SDT_OTHER 0x46 #define TABLE_ID_BAT 0x4a #define TABLE_ID_EIT_PF_ACTUAL 0x4e #define TABLE_ID_EIT_PF_OTHER 0x4f #define TABLE_ID_EIT_ACTUAL_START 0x50 #define TABLE_ID_EIT_ACTUAL_END 0x5f #define TABLE_ID_EIT_OTHER_START 0x60 #define TABLE_ID_EIT_OTHER_END 0x6f #define TABLE_ID_PAT 0x00 #define TABLE_ID_EIT_PF_FREESAT 0xd1 /* * Descriptor ID's that we care about... */ #define VIDEO_STREAM_DESC 0x02 #define AUDIO_STREAM_DESC 0x03 #define DATA_STREAM_ALIGNMENT 0x06 #define TARGET_BACK_GRID 0x07 #define CA_DESC 0x09 #define ISO_639_LANG 0x0A #define MAX_BITRATE 0x0E #define PRIVATE_DATA_INDICATOR 0x0F #define STD_DESC 0x11 #define CAROUSEL_IDENT 0x13 #define NETWORK_NAME 0x40 #define SERVICE_LIST 0x41 #define SATELLITE_DELIVERY 0x43 #define BOUQUET_NAME 0x47 #define SERVICE_DESC 0x48 #define COUNTRY_AVAILABILITY 0x49 #define LINKAGE_DESC 0x4A #define NVOD_REFERENCE 0x4B #define TIME_SHIFED_SERVICE 0x4C #define SHORT_EVENT 0x4D #define COMPONENT_DESC 0x50 #define STREAM_IDENT 0x52 #define CA_IDENT 0x53 #define CONTENT_DESC 0x54 #define TELETEXT_DESC 0x56 #define SUBTITLING 0x59 #define TERRESTRIAL_DELIV 0x5A #define PRIVATE_DATA_SPECIFIER 0x5F #define FREQ_LIST 0x62 #define DATA_BROADCAST 0x64 #define DATA_BROADCAST_ID 0x66 #define AC3_DESC 0x6A #define DEFAULT_AUTHORITY 0x73 #define CONTENT_IDENTIFIER 0x76 #define LOGICAL_CHANNEL 0x83 /* * Ones that we don't have details on... */ // C3 is not shown with a private data specifier??? #define TODO_C3 0xC3 // Seems to be text saying "More power to your finger" #define TODO_FE 0xFE // Looks like a duplicate of private_data_indicator #define SKY 0x00000002 // Sky/Astra Private Data Specifier #define TODO_B2 0xB2 // Looks like a Sky private data thing? (variable length) #define TODO_C0 0xC0 // On Sky, seems to be just a channel name #define TODO_FE 0xFE // Sky component name thing (4 chars: AUD1, SUB etc) #define FREEVIEW 0x0000233a // Freeview Private Data Specifier #define TODO_86 0x86 // ?? #define TODO_90 0x90 #define TODO_91 0x91 /* * Freesat Private Descriptors? */ #define BBC 0x46534154 // Freesat Private Data Specifier #define FREESAT_D1 0xD1 // Freesat SI table identifier? #define FREESAT_D3 0xD3 // Freesat LCN descriptor (plus more?) #define FREESAT_D4 0xD4 // Freesat Region Listing?? #define FREESAT_D7 0xD7 // Config info??? #define FREESAT_D9 0xD9 // Freesat Short Service Name? /* * Generic table numbers... */ #define DVB_TABLE_PID_PAT 0x00 #define DVB_TABLE_SDT 0x11 #define DVB_TABLE_BAT 0x11 #define DVB_TABLE_EIT 0x12 /* * Freesat mapped SI table numbers (for the FREESAT_D1 descriptor) */ #define FREESAT_EIT 0x01 #define FREESAT_EITpf 0x02 #define FREESAT_BAT 0x03 #define FREESAT_SDT 0x04 #define FREESAT_TDT 0x05 #define FREESAT_TOT 0x06 #define FREESAT_NIT 0x07 /* * DSM-CC stuff.. */ #define DSMCC_TYPE_DOWNLOAD_MESSAGE 0x3 #define DATA_BROADCAST_ID_BOOT 0x0106 #define TAG_BIOP 0x49534f06 #define TAG_CONN_BINDER 0x49534f40 #define TAG_OBJECT_LOCATION 0x49534f50 #define DSMCC_DOWNLOAD_INFO_REQUEST 0x1001 #define DSMCC_DOWNLOAD_INFO_RESPONSE 0x1002 #define DSMCC_DOWNLOAD_INFO_INDICATION 0x1002 #define DSMCC_DOWNLOAD_DATA_BLOCK 0x1003 #define DSMCC_DOWNLOAD_DATA_REQUEST 0x1004 #define DSMCC_DOWNLOAD_CANCEL 0x1005 #define DSMCC_DOWNLOAD_SERVER_INITIATE 0x1006 #define INIT_DESCRIPTORS_WITH_END(pointer, end) { \ uint32_t private_data_specifier = 0; \ while( pointer < end ) { \ int tag = pointer[0]; \ int length = pointer[1]; \ pointer += 2; \ if(tag == PRIVATE_DATA_SPECIFIER) { \ private_data_specifier=(pointer[0]<<24)|(pointer[1]<<16)|(pointer[2]<<8)|pointer[3]; \ } #define INIT_DESCRIPTORS(pointer) \ uint8_t *_desc_end = pointer + 2 + ((pointer[0]&0x0f)<<8|pointer[1]); \ pointer+=2; \ INIT_DESCRIPTORS_WITH_END(pointer, _desc_end) #define DESCRIPTOR(v) \ else if(tag == v) #define END_DESCRIPTORS(pointer) \ else { \ if(1) { DP_ERROR("unhandled descriptor tag: 0x%02x (len=%d) [privdataspec=%08x]", tag, length, private_data_specifier ); \ int __i; for(__i=0;__idata; \ uint8_t *end = p + packet->size - 4; end; \ int table_id = p[0]; table_id; \ int table_id_extension = (p[3]<<8)|p[4]; table_id_extension; \ int version_number = (p[5]&0x3e)>>1; version_number; \ int current_next_indicator = p[5]&0x01; current_next_indicator; \ int section_number = p[6]; section_number; \ int last_section_number = p[7]; last_section_number; \ p += 8; #define SI_TABLE_SHORT_FIELDS \ int table_id = p[0]; table_id; \ fprintf(stderr, "DECODING TABLE %02x (short fields)\n", table_id ); \ p += 1; /*---------------------------------------------------------------------------------------- * We'll define the freesat decode info here... *---------------------------------------------------------------------------------------- */ extern int freesat_huffman_decode( const unsigned char *src, size_t size, char *uncompressed, int uncompressed_len); /*---------------------------------------------------------------------------------------- * We'll create a thread that keeps an eye on the incoming si queue for packets and then * processes them accordingly, so we need a queue and thread for that... *---------------------------------------------------------------------------------------- */ static pthread_t si_incoming_thread; //static dp_queue si_incoming_queue; static struct core_data_list si_incoming_queue; /*---------------------------------------------------------------------------------------- * This is the main structure for holding the program/channel information, this forms the main * reference for pretty much everything we do since this is usually channel driven. * * We also have a lock here so that we can access the use the data without worrying about * it changing under our feet. * * Plus: * A central CRID list to reduce the space used by each event *---------------------------------------------------------------------------------------- */ static struct core_data_hash si_programs; static pthread_mutex_t si_programs_lock; static struct core_data_hash si_crids; static pthread_mutex_t si_crids_lock; /* * The transport streams are a network independent list, they contain all the info about * the transport itself */ static struct core_data_hash si_transports; /*---------------------------------------------------------------------------------------- * Create and destroy the basic event structure... *---------------------------------------------------------------------------------------- */ struct si_event *create_si_event() { struct si_event *item = malloc(sizeof(struct si_event)); if(!item) { DP_ERROR("unable to malloc for dp_si_event"); return NULL; } memset(item, 0, sizeof(struct si_event)); return(item); } struct si_event *find_or_create_si_event(struct core_data_list *event_list, uint16_t event_id) { struct si_event *event = core_data_list_item_find(event_list,struct si_event, item->event_id == event_id); if(!event) { event = create_si_event(); if(!event) return NULL; event->event_id = event_id; core_data_list_add_item(event_list, event); } return event; } /*---------------------------------------------------------------------------------------- * Create and find the transport stream structure... *---------------------------------------------------------------------------------------- */ struct si_transport_stream *create_si_transport_stream() { struct si_transport_stream *item = (struct si_transport_stream *)malloc(sizeof(struct si_transport_stream)); if(!item) { DP_ERROR("unable to malloc for si_transport_stream"); return NULL; } memset(item, 0, sizeof(struct si_transport_stream)); // core_data_init_hash(&item->services, NULL); return item; } struct si_transport_stream *find_or_create_si_transport_stream(uint16_t transport_stream_id, uint16_t original_network_id) { struct si_transport_stream *ts = core_data_hash_item_find(&si_transports, CORE_DATA_HASHi2(transport_stream_id, original_network_id), struct si_transport_stream, item->transport_stream_id == transport_stream_id && item->original_network_id == original_network_id); if(!ts) { ts = create_si_transport_stream(); if(ts) { ts->transport_stream_id = transport_stream_id; ts->original_network_id = original_network_id; core_data_hash_add_item(&si_transports, ts, CORE_DATA_HASHi2(transport_stream_id, original_network_id)); } } return ts; } struct si_transport_stream *find_si_transport_stream(uint16_t transport_stream_id, uint16_t original_network_id) { struct si_transport_stream *ts = core_data_hash_item_find(&si_transports, CORE_DATA_HASHi2(transport_stream_id, original_network_id), struct si_transport_stream, item->transport_stream_id == transport_stream_id && item->original_network_id == original_network_id); } /*---------------------------------------------------------------------------------------- * Create and destroy the basic program structure... *---------------------------------------------------------------------------------------- */ struct si_program *create_si_program() { struct si_program *item = (struct si_program *)malloc(sizeof(struct si_program)); if(!item) { DP_ERROR("unable to malloc for dp_si_program"); return NULL; } memset(item, 0, sizeof(struct si_program)); core_data_init_list(&item->events, NULL); core_data_init_list(&item->tuners, NULL); pthread_mutex_init(&item->__lock, NULL); return(item); } void free_si_program(void *_item) { struct si_program *item = (struct si_program *)_item; core_data_free_entire_list(&item->events); core_data_free_entire_list(&item->tuners); } /* * We use this to find (and create if it doesn't exist) the relevant program entry, it will * always return with the si_program locked. It also only locks the si_programs_lock if it add's * an item, since we know this will only be called from the SI thread and it's the only thread * that can update. */ struct si_program *find_or_create_si_program(uint16_t program_id, uint16_t transport_stream_id, uint16_t original_network_id) { // lock_mutex(&si_programs_lock); struct si_program *program = core_data_hash_item_find(&si_programs, CORE_DATA_HASHi3(program_id, transport_stream_id, original_network_id), struct si_program, item->program_id == program_id && item->transport_stream_id == transport_stream_id && item->original_network_id == original_network_id); if(!program) { program = create_si_program(); if(program) { program->program_id = program_id; program->transport_stream_id = transport_stream_id; program->original_network_id = original_network_id; lock_mutex(&si_programs_lock); core_data_hash_add_item(&si_programs, program, CORE_DATA_HASHi3(program_id, transport_stream_id, original_network_id)); pthread_mutex_unlock(&si_programs_lock); } } if(program) lock_mutex(&program->__lock); // pthread_mutex_unlock(&si_programs_lock); return program; } /* * This will scan find an si_program structure but also won't lock the si_programs_lock, this should only * be used within the si_thread (hence the inline bit) */ static inline struct si_program *find_si_program_nolock(uint16_t program_id, uint16_t transport_stream_id, uint16_t original_network_id) { struct si_program *program = core_data_hash_item_find(&si_programs, CORE_DATA_HASHi3(program_id, transport_stream_id, original_network_id), struct si_program, item->program_id == program_id && item->transport_stream_id == transport_stream_id && item->original_network_id == original_network_id); if(program) lock_mutex(&program->__lock); return program; } /* * This is for external use... and will also return the program locked but will lock the si_programs * list so that we don't get into trouble with the si_thread updating it. */ struct si_program *find_si_program(uint16_t program_id, uint16_t transport_stream_id, uint16_t original_network_id) { lock_mutex(&si_programs_lock); struct si_program *program = find_si_program_nolock(program_id, transport_stream_id, original_network_id); pthread_mutex_unlock(&si_programs_lock); return program; } struct si_program *find_si_program_by_lcn(int lcn) { lock_mutex(&si_programs_lock); struct si_program *program = core_data_hash_item_scanfor(&si_programs, struct si_program, item->logical_channel_number == lcn); if(!program) { DP_ERROR("channel not found %d", lcn ); } if(program) lock_mutex(&program->__lock); pthread_mutex_unlock(&si_programs_lock); return program; } void unlock_si_program(struct si_program *program) { if(program) pthread_mutex_unlock(&program->__lock); } /*---------------------------------------------------------------------------------------- * Some helpers for the crid table... crid_find will locate the crid (and add a new one * if needed) *---------------------------------------------------------------------------------------- */ struct si_crid *crid_find(char *crid) { lock_mutex(&si_crids_lock); struct si_crid *item = (struct si_crid *)core_data_hash_item_find(&si_crids, CORE_DATA_HASHstr(crid), struct si_crid, strcmp(item->crid, crid)==0); if(!item) { item = (struct si_crid *)malloc(sizeof(struct si_crid)); if(item) { strcpy(item->crid, crid); } core_data_hash_add_item(&si_crids, item, CORE_DATA_HASHstr(crid)); } pthread_mutex_unlock(&si_crids_lock); return item; } /*---------------------------------------------------------------------------------------- * OK, this is a new way of tracking the tables, it now waits for a complete table before * letting the decoding routines do anything (so they will need to handle multiple sections) * * This may slow our startup down a bit (since we wait for all the table), but it should * mean we are a bit more consistent. *---------------------------------------------------------------------------------------- */ static struct core_data_hash si_tables; /*---------------------------------------------------------------------------------------- * We need to convert between MJD+UTC dates and system times, so some utility functions... *---------------------------------------------------------------------------------------- */ time_t dvb_time_to_time(uint8_t *p) { uint16_t mjd = p[0]<<8|p[1]; struct tm in_time; time_t rc; long y, m, d, k; y = (long)((mjd - 15078.2) / 365.25); m = (long)((mjd - 14956.1 - (long)(y * 365.25)) / 30.6001); d = (long)(mjd - 14956 - (long)(y * 365.25) - (long)(m * 30.6001)); k = (m == 14 || m == 15) ? 1 : 0; y += k; m = m - 2 - k*12; in_time.tm_sec = (((p[4]&0xf0)>>4)*10)+(p[4]&0x0f); in_time.tm_min = (((p[3]&0xf0)>>4)*10)+(p[3]&0x0f); in_time.tm_hour = (((p[2]&0xf0)>>4)*10)+(p[2]&0x0f); in_time.tm_mday = d; in_time.tm_mon = m; in_time.tm_year = y; // in_time.tm_zone = NULL; rc = timegm(&in_time); return rc; } /*---------------------------------------------------------------------------------------- * MHEG5 -- Process the user-network table *---------------------------------------------------------------------------------------- */ //#define INT32(p) ((p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]) //#define INT16(p) ((p[0]<<8)|p[1]) //#define INT8(p) (p[0]) #define INT32(p) ({ p+=4; (p[-4]<<24)|(p[-3]<<16)|(p[-2]<<8)|p[-1]; }) #define INT16(p) ({ p+=2; (p[-2]<<8)|p[-1]; }) #define INT8(p) (*p++) int process_dsmcc_un(struct si_data_packet *packet) { SI_TABLE_STANDARD_FIELDS int i; /* * First pull out the DSM-CC Message header... */ int protocol_discriminator = INT8(p); int dsmcc_type = INT8(p); int message_id = INT16(p); uint32_t transaction_id = INT32(p); p++; // reserved byte int adaption_length = INT8(p); int message_length = INT16(p); p += adaption_length; fprintf(stderr, "UN: pd=%d type=%d id=%d tid=%04x adap_len=%d mess_len=%d\n", protocol_discriminator, dsmcc_type, message_id, transaction_id, adaption_length, message_length); if(dsmcc_type = DSMCC_TYPE_DOWNLOAD_MESSAGE) { if(message_id == DSMCC_DOWNLOAD_DATA_BLOCK) { fprintf(stderr, "DATA BLOCK\n"); } else if(message_id == DSMCC_DOWNLOAD_INFO_INDICATION) { fprintf(stderr, "INFO INDICATION\n"); uint32_t download_id = INT32(p); int block_size = INT16(p); int window_size = INT8(p); int ack_period = INT8(p); uint32_t download_window = INT32(p); uint32_t download_scenario = INT32(p); /* * Compatibility descriptor .. can be ignored for the UK MHEG spec... */ int cd_length = INT16(p); if(cd_length) { int cd_count = INT16(p); fprintf(stderr, "cd_length=%d count=%d\n", cd_length, cd_count); /* * Compatibility descriptors... the UK spec say we can ignore these for MHEG */ for(i=0; i < cd_count; i++) { int desc_type = INT8(p); int desc_len = INT8(p); fprintf(stderr, "Compat desc %d, type=%d len=%d\n", i, desc_type, desc_len); p+= desc_len; } } int number_of_modules = INT16(p); fprintf(stderr, "did=%d bs=%d ws=%d period=%d dw=%d ds=%d nom=%d\n", download_id, block_size, window_size, ack_period, download_window, download_scenario, number_of_modules); for(i=0; i < number_of_modules; i++) { int module_id = INT16(p); uint32_t module_size = INT32(p); int module_version = INT8(p); int module_info_length = INT8(p); fprintf(stderr, "mod_id=%d size=%d version=%d info=[%s]\n", module_id, module_size, module_version, hex_dump(p, module_info_length)); /* * The UK MHEG spec has the BIOP:ModuleInfo structure stored in here... */ uint8_t *m = p; uint32_t module_timeout = INT32(m); uint32_t block_timeout = INT32(m); uint32_t min_block_time = INT32(m); int taps_count = INT8(m); fprintf(stderr, "module_timeout=%d block_timeout=%d min_block_time=%d taps_count=%d\n", module_timeout, block_timeout, min_block_time, taps_count); int j; for(j=0; j < taps_count; j++) { /* * The UK standard seems to have use and id the wrong way round??? */ int id = INT16(m); int use = INT16(m); int assoc_tag = INT16(m); int selector_length = INT8(m); m+= selector_length; fprintf(stderr, "TAP %d -- use=0x%x id=%d assocTag=%d selector_len=%d\n", j, use, id, assoc_tag, selector_length); } p += module_info_length; } /* * Now a descriptor loop... */ int user_info_len = INT8(p); fprintf(stderr, "DESCRIPTOR LEN %d\n", user_info_len); INIT_DESCRIPTORS_WITH_END(p, p+user_info_len) END_DESCRIPTORS(p) } else if(message_id == DSMCC_DOWNLOAD_SERVER_INITIATE) { fprintf(stderr, "SERVER INITIATE\n"); uint8_t server_id[20]; memcpy(server_id, p, 20); p+=20; /* * Compatibility descriptor .. can be ignored for the UK MHEG spec... */ int cd_length = p[0]<<8|p[1]; p+=2; if(cd_length) { int cd_count = p[0]<<8|p[1]; p+=2; fprintf(stderr, "cd_length=%d count=%d\n", cd_length, cd_count); /* * Compatibility descriptors... the UK spec say we can ignore these for MHEG */ for(i=0; i < cd_count; i++) { int desc_type = p[0]; int desc_len = p[1]; fprintf(stderr, "Compat desc %d, type=%d len=%d\n", i, desc_type, desc_len); p+= desc_len + 2; } } int private_data_len = p[0]<<8|p[1]; p+=2; /* * IOP::IOR */ int type_id_length = INT32(p); char type_id[255]; strncpyz(type_id, p, type_id_length); p+=type_id_length; int tagged_profiles_count = INT32(p); fprintf(stderr, "HAVE IOPIOR: [%s] (%d)\n", type_id, type_id_length); fprintf(stderr, "PROFILECOUNT %d\n", tagged_profiles_count); for(i=0; i < tagged_profiles_count; i++) { uint32_t profile_id_tag = INT32(p); int profile_data_length = INT32(p); fprintf(stderr, "TAG %d, id=%04x -- profile [%s]\n", i, profile_id_tag, hex_dump(p, profile_data_length)); if(profile_id_tag == TAG_BIOP) { /* * BIOP Profile Body */ int profile_data_byte_order = INT8(p); int lite_component_count = INT8(p); /* TODO: use the count above and proceduralise all of this... */ /* * Object Location... */ uint32_t component_id_tag = INT32(p); int component_data_length = INT8(p); if(component_id_tag == TAG_OBJECT_LOCATION) { uint32_t carousel_id = INT32(p); int module_id = INT16(p); int version_major = INT8(p); int version_minor = INT8(p); int object_key_length = INT8(p); char object_key_data[MAX_SI_STRING]; memcpy(object_key_data, p, object_key_length); p+=object_key_length; fprintf(stderr, "OBJ LOCATION: carid=%d moduleid=%d ver=%d/%d okl=%d [%s]\n", carousel_id, module_id, version_major, version_minor, object_key_length, hex_dump(object_key_data, object_key_length)); } else { DP_ERROR("unknown component_id_tag: %08x", component_id_tag); p+= component_data_length; } /* * ConnBinder */ component_id_tag = INT32(p); component_data_length = INT8(p); if(component_id_tag == TAG_CONN_BINDER) { int taps_count = INT8(p); /* * We only care about the first tap... */ int id = INT16(p); int use = INT16(p); int assoc_tag = INT16(p); int selector_length = INT8(p); int selector_type = INT16(p); uint32_t transaction_id = INT32(p); uint32_t timeout = INT32(p); fprintf(stderr, "TAP: id=%d use=%d assoctag=%d sel_len=%d sel_type=%d\n", id, use, assoc_tag, selector_length, selector_type); p+=component_data_length - 18; } else { DP_ERROR("unknown component_id_tag: %08x", component_id_tag); p+=component_data_length; } } else { DP_ERROR("unknown profile_id: %08x", profile_id_tag); p += profile_data_length; } } int download_taps_count = INT8(p); fprintf(stderr, "Download tap count=%d\n", download_taps_count); } } else { fprintf(stderr, "UNHANDLE DSMCC TYPE: %d\n", dsmcc_type); } } int process_dsmcc_download(struct si_data_packet *packet) { SI_TABLE_STANDARD_FIELDS } /*---------------------------------------------------------------------------------------- * We need to handle MHEG-5 as a special case so this routine handles the 13818-6 streams * from the PMT *---------------------------------------------------------------------------------------- */ uint8_t *process_mheg5_pmt(struct tuner_info *tuner, int program_id, int elementary_pid, uint8_t *p, uint8_t *end) { fprintf(stderr, "MHEG5 Potential on program %04x epid=%d\n", program_id, elementary_pid); INIT_DESCRIPTORS(p) DESCRIPTOR(STREAM_IDENT) { int stream_tag = p[0]; fprintf(stderr, "TAG=%d\n", stream_tag); } DESCRIPTOR(DATA_BROADCAST_ID) { uint8_t selector[MAX_SI_STRING]; int broadcast_id = INT16(p); if(broadcast_id == DATA_BROADCAST_ID_BOOT) { uint8_t *m = p; while(m < p+length) { int application_type_code = INT16(m); int boot_priority_hint = INT8(m); int application_specific_data_length = INT8(m); fprintf(stderr, "APPLICATION type=%d boot_pri_hint=%d data_len=%d [%s]\n", application_type_code, boot_priority_hint, application_specific_data_length, hex_dump(m, application_specific_data_length)); m+=application_specific_data_length; } } else { DP_ERROR("unhandled DATA_BROADCAST_ID type: %04x\n", broadcast_id); } } DESCRIPTOR(CAROUSEL_IDENT) { uint32_t carousel_ident = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; fprintf(stderr, "CAROUSEL IDENT: %04x\n", carousel_ident); /* * Hack to add any pid with a carousel ident to the mix... */ tuner_cmd(tuner, TUNER_CMD_ADD_SI_PID, elementary_pid); } END_DESCRIPTORS(p) return p; } /*---------------------------------------------------------------------------------------- * The PAT tells us where to look for the PMT's for the channels ... at this stage we * can't put anything in the database since we don't know the network_id, so we'll register * the streams for the PMT's and use the PMT/SDT parser to properly populate the channel * information. * * This will also drive the NIT listener if we get a channel 0 reference *---------------------------------------------------------------------------------------- */ int process_pat(struct si_data_packet *packet) { SI_TABLE_STANDARD_FIELDS /* * The PAT is just about mapping service ID's to PMT's, so we don't need anything else to start * listening to the PMT's. */ while( p < end ) { uint16_t program_number = p[0]<<8|p[1]; int16_t pid = (p[2]&0x1f)<<8|p[3]; // core_input_alloc_stream(tuner->input_context, pid, NULL); tuner_cmd(packet->tuner, TUNER_CMD_ADD_SI_PID, pid); p+=4; fprintf(stderr, "Program Number %d, pid %d (p=%p end=%p)\n", program_number, pid, p, end); } return 1; } /*---------------------------------------------------------------------------------------- * The program map table identifies the pid's that contain some kind of media for the * channel ... at this stage there are just loads of streams, hopefully we'll use the * SDT to turn this into something more useful. * * We also look at the Freesat 0xD1 descriptor in here to point us at the Freesat SI * tables (which annoyingly reside on different pids!) *---------------------------------------------------------------------------------------- */ int process_pmt(struct si_data_packet *packet) { SI_TABLE_STANDARD_FIELDS int program_id = table_id_extension; int transport_stream_id = packet->tuner->current_transport_stream_id; int original_network_id = packet->tuner->current_original_network_id; /* * Now find or create our si_service structure, we'll reset the media streams since we'll always * rewrite them in this routine... */ struct si_program *program = find_or_create_si_program(program_id, transport_stream_id, original_network_id); if(!program) return 0; program->pmt_pid = packet->pid; program->media_stream_count = 0; program->pcr_pid = (p[0]&0x1f)<<8|p[1]; p += 2; /* * We know the pmt fits in one section, so we can do this... */ program->have_pmt = 1; /* * First we run through the standard descriptors these tend to be MPEG ones... */ INIT_DESCRIPTORS(p) DESCRIPTOR(MAX_BITRATE) { /* Do we care about this? */ } END_DESCRIPTORS(p) /* * Now we iterate over the streams and store the relevant information in our * structure */ fprintf(stderr, "PMT - prodig=%d\n", program_id); while( p < end ) { int elementary_pid = (p[1]&0x1f)<<8|p[2]; int stream_type = p[0]; int codec_type = CODEC_TYPE_UNKNOWN; int codec_id = CODEC_ID_NONE; struct si_media_stream_info *stream = NULL; p += 3; if(stream_type == STREAM_TYPE_AUDIO_MPEG1 || stream_type == STREAM_TYPE_AUDIO_MPEG2) { codec_type = CODEC_TYPE_AUDIO; codec_id = CODEC_ID_MP2; } else if(stream_type == STREAM_TYPE_VIDEO_MPEG1 || stream_type == STREAM_TYPE_VIDEO_MPEG2) { codec_type = CODEC_TYPE_VIDEO; codec_id = CODEC_ID_MPEG2VIDEO; } else if(stream_type == STREAM_TYPE_VIDEO_H264) { codec_type = CODEC_TYPE_VIDEO; codec_id = CODEC_ID_H264; } else if(stream_type == STREAM_TYPE_PRIVATE_SECTION) { /* * We need to look through this since it may be hiding a freesat descriptor that we need */ } else if(stream_type == STREAM_TYPE_PRIVATE_DATA) { /* * Could be subtitles, teletext or even AC3 audio */ codec_type = CODEC_TYPE_UNKNOWN; codec_id = CODEC_ID_NONE; } else if(stream_type == STREAM_TYPE_ISO13818_6_B) { /* * This will generally be our MHEG-5 stuff, so we need to handle this separately since * we store this info outside of the media_stream info. TODO: should we? */ // p = process_mheg5_pmt(packet->tuner, program_id, elementary_pid, p, end); DP_ERROR("PID %d, skipping mheg descriptors", elementary_pid); SKIP_DESCRIPTORS(p); continue; } else { DP_ERROR("PID %d, stream type %d unhandled, skipping", elementary_pid, stream_type); SKIP_DESCRIPTORS(p); continue; } /* * For anything other than the freesat streams we'll create a new media stream info * structure so we can store all the nice useful stuff. */ if(stream_type != STREAM_TYPE_PRIVATE_SECTION) { stream = &program->media_streams[program->media_stream_count++]; stream->pid = elementary_pid; stream->type = stream_type; stream->codec_type = codec_type; stream->codec_id = codec_id; fprintf(stderr, "Now have %d media streams, type is %d (%d)\n", program->media_stream_count, stream->type, stream->pid ); } /* * Now run through the descriptor loop... */ INIT_DESCRIPTORS(p) DESCRIPTOR(ISO_639_LANG) { /* * If we get a language descriptor then it's an audio stream... we'll assume MP2 audio * as a default (unless we get an AC3 descriptor later) */ fprintf(stderr, "PID %d - AUDIO LANG (%d)\n", elementary_pid, p[3]); strncpyz(stream->info.audio.language, p, 3); stream->info.audio.type = (int)p[3]; stream->codec_type = CODEC_TYPE_AUDIO; stream->codec_id = CODEC_ID_MP2; } DESCRIPTOR(AC3_DESC) { /* * This is an AC-3 stream, not sure what we need to do with the fields in here: See TS 102 366 */ fprintf(stderr, "PID %d - AC3\n", elementary_pid); stream->codec_type = CODEC_TYPE_AUDIO; stream->codec_id = CODEC_ID_AC3; } DESCRIPTOR(CA_DESC) { uint16_t ca_system_id = (p[0]<<8)|p[1]; uint16_t ca_pid = (p[2]&0x1f)<<8|p[3]; int private_data_len = length - 4; fprintf(stderr, "CA descriptor -- system_id=%d pid=%d private_data_len=%d\n", ca_system_id, ca_pid, private_data_len); } DESCRIPTOR(STREAM_IDENT) { if(stream) stream->tag = (int)p[0]; } DESCRIPTOR(SUBTITLING) { uint8_t *sub_p = p; int i = 0; while(sub_p < p+length) { strncpyz(stream->info.subtitles.detail[i].language, sub_p, 3); stream->info.subtitles.detail[i].type = sub_p[3]; stream->info.subtitles.detail[i].composition_page_id = sub_p[4]<<8|sub_p[5]; stream->info.subtitles.detail[i].ancillary_page_id = sub_p[6]<<8|sub_p[7]; i++; sub_p += 8; } stream->info.subtitles.count = i; stream->codec_type = CODEC_TYPE_SUBTITLE; stream->codec_id = CODEC_ID_DVB_SUBTITLE; } DESCRIPTOR(TARGET_BACK_GRID) { stream->info.video.background_grid_horizontal_size = p[0]<<6|(p[1]&0xfc)>>2; stream->info.video.background_grid_vertical_size = p[1]&0x02<<12|p[2]<<4|(p[3]&0xf0)>>4; stream->info.video.background_grid_aspect_ratio = p[3]&0x0f; } DESCRIPTOR(TELETEXT_DESC) { /* Don't care about teletext for now */ } DESCRIPTOR(CAROUSEL_IDENT) { /* TODO: This is to do with MHEG (see 13818-6) */ } DESCRIPTOR(MAX_BITRATE) { /* Do we care about this? */ } DESCRIPTOR(DATA_STREAM_ALIGNMENT) { /* Not sure we need to do anything with this either */ } DESCRIPTOR(PRIVATE_DATA_INDICATOR) { //stream->private_data_indicator = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; //fprintf(stderr, "private_data_indicator: %08x [%c%c%c%c]\n", stream->private_data_indicator, // p[0], p[1], p[2], p[3]); } DESCRIPTOR(TODO_FE) { /* * Looks the same as a PRIVATE_DATA_INDICATOR */ uint32_t fe =(p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; fprintf(stderr, "private_descriptor_fe: %08x [%c%c%c%c]\n", fe, p[0], p[1], p[2], p[3]); } PRIV_DESCRIPTOR(BBC, FREESAT_D1) { /* * This is a Freesat descriptor that points us a PID's for the Freesat SI tables, * so all we need to do is add a new stream... */ int i; for(i=0; iinput_context->stream[elementary_pid]) core_input_alloc_stream(tuner->input_context, elementary_pid, NULL); tuner_cmd(packet->tuner, TUNER_CMD_ADD_SI_PID, elementary_pid); break; } } } PRIV_DESCRIPTOR(SKY, TODO_FE) { /* * This is a 4 character identifier (or 3) that describes the stream (VID, AUD1 etc) * I don't think we need to do anything with this for now. */ } DESCRIPTOR(DATA_BROADCAST_ID) { /* * This seems to be used to map data information in the 'channel' to a particular stream??? */ if(stream) { stream->info.data.broadcast_id = p[0]<<8|p[1]; stream->info.data.selector_length = p[2]; strncpy((char *)stream->info.data.selector, (char *)p+3, length-2); } } DESCRIPTOR(STD_DESC) { /* Not sure about this one, so ignoring for now */ } END_DESCRIPTORS(p) } unlock_si_program(program); tuner_mgr_cmd(packet->tuner, TUNER_REQUEST_STREAM_INFO, program->program_id, program->transport_stream_id, program->original_network_id); return 1; } /*---------------------------------------------------------------------------------------- * The BAT has some useful information about the services in it, especially for Freesat * we should know which Bouquet and Region we want before we process this otherwise we're * not going to be able to work out the right LCN's for Freesat. * * We return a 0 if we want to forget about the whole BAT sub-table, so we can look at * the name and region list and abort the section if needed, if it's a Freesat table * (which we can tell by see if it's a non standard PID.) *---------------------------------------------------------------------------------------- */ void process_bat(struct si_data_packet *packet) { SI_TABLE_STANDARD_FIELDS int bouquet_id = table_id_extension; /* * TODO, we need to store this configuration somewhere properly... */ static int wanted_region_id = 0x1c; // [South/Meridan S] static int wanted_bouquet_id = 272; // [England HD] fprintf(stderr, "BATTY: id=%d (pid=%d section=%d)\n", bouquet_id, packet->pid, section_number); /* * For freesat, we only care about our chosen Bouquet... */ if(packet->tuner->scope == TUNER_SCOPE_SATELLITE) { if(packet->pid == DVB_TABLE_BAT) { /* * This is the normal Astra BAT, don't care about this one... */ return; } if(bouquet_id != wanted_bouquet_id) { fprintf(stderr, "ABORTING THIS BAT SUBTABLE (%d), isn't our selected bouquet.\n", bouquet_id); return; } } /* * Process the main bouquet descriptor loop */ INIT_DESCRIPTORS(p) DESCRIPTOR(BOUQUET_NAME) { char name[MAX_SI_STRING]; strncpyz(name, p, length); fprintf(stderr, "BOUQUET NAME: [%s]\n", name); } DESCRIPTOR(COUNTRY_AVAILABILITY) { int for_these_countries = (p[0]&0x80)>>7; char country[4]; country[3] = '\0'; uint8_t *d = p+1; while(d < p + length) { country[0] = d[0]; country[1] = d[1]; country[2] = d[2]; fprintf(stderr, "COUNTRY AVAILABILITY: (%d) -> %s\n", for_these_countries, country); d += 3; } } DESCRIPTOR(LINKAGE_DESC) { int tsid = p[0]<<8|p[1]; int onid = p[2]<<8|p[3]; int sid = p[4]<<8|p[5]; int linkage_type = p[6]; fprintf(stderr, "LINKAGE tsid=%d onid=%d sid=%d type=%d\n", tsid, onid, sid, linkage_type); } PRIV_DESCRIPTOR(BBC, FREESAT_D7) { /* * This looks like some kind of config detail, it seems to point to a specific * transport stream... */ uint16_t size = p[0]<<8|p[1]; uint16_t config_stream_id = p[2]<<8|p[3]; uint16_t config_onid = p[4]<<8|p[5]; fprintf(stderr, " Freesat D7: size=%d streamid=0x%04x/%d onid=0x%04x ...plus more\n", size, config_stream_id, config_stream_id, config_onid, config_onid); } PRIV_DESCRIPTOR(BBC, FREESAT_D4) { /* * This looks like a region listing, we seem to have a number, a language and then the text */ int region_number; char lang[4]; char text[MAX_SI_STRING]; uint8_t *d = p; while(d < p+length) { region_number = (d[0]<<8)|d[1]; strncpyz(lang, d+2, 3); strncpyz(text, d+6, d[5]); fprintf(stderr, "D2: 0x%04x/%04d [%s] [%s]\n", region_number, region_number, lang, text); d += 6 + d[5]; } } END_DESCRIPTORS(p) /* * Now the ts loop... */ p+=2; while(p < end) { int transport_stream_id = (p[0]<<8)|p[1]; int original_network_id = (p[2]<<8)|p[3]; // fprintf(stderr, "BOUQUET %d, tsid=%d onid=%d\n", bouquet_id, transport_stream_id, original_network_id); p+=4; INIT_DESCRIPTORS(p) DESCRIPTOR(SERVICE_LIST) { uint8_t *d = p; while(d < p + length) { int service_id = (d[0]<<8)|d[1]; int service_type = d[2]; // fprintf(stderr, " Service 0x%04x/%d, type %d\n", service_id, service_id, service_type); d+=3; /* * TODO: on freesat this is used to populate the main service list */ } } PRIV_DESCRIPTOR(BBC, FREESAT_D3) { /* * Not sure about this one, but you can pull out the LCN at the very least... * * Where the extra data is exists it looks like we have alternate LCN's for channels * that get moved depending on region. * * Could it be lcn (in region) lcn (in region) where ffff means any other region * not sure where we see two lcn's both with ffff shown * * The flags may signify add or remove somehow, plus also seem to change depending on * service type? Perhaps also running status. */ uint8_t *d = p; while(d < p+length) { int service_id = (d[0]<<8)|d[1]; int unknown_16 = (d[2]<<8)|d[3]; // possibly bit flags int detail_len = d[4]; d += 5; struct si_program *program = find_or_create_si_program(service_id, transport_stream_id, original_network_id); if(!program) return; // fprintf(stderr, "LCN (0x%04x/%d) -- unknown16=%04x: /", service_id, service_id, unknown_16); int i; for(i=0; i < detail_len; i+=4) { int unknown_4 = (d[0]&0xf0) >> 4; int lcn = (d[0]&0x0f)<<8|d[1]; int region = d[2]<<8|d[3]; if(region == wanted_region_id || region == 0xffff) { /* * This looks like an LCN for us... */ program->logical_channel_number = lcn; fprintf(stderr, "FREESAT-LCN: service 0x%04x/%d -- LCN=%d\n", service_id, service_id, lcn); } d += 4; // fprintf(stderr, "(?%x) [%d] in %04x/", unknown_4, lcn, region); } unlock_si_program(program); } } DESCRIPTOR(DEFAULT_AUTHORITY) { // TODO: store this somewhere??? // strncpyz(program->authority, p, length); } END_DESCRIPTORS(p) } return; } /*---------------------------------------------------------------------------------------- * The service description table gives us a lot of useful information about each channel * and hopefully fills in the blanks that we need to make channels available in a more * user friendly form. * For Freeview we need to use the standard PID's, but for Freesat we want to ignore what * we see on the standard PID's (since it's Astra not Freesat.) *---------------------------------------------------------------------------------------- */ void process_sdt(struct si_data_packet *packet) { SI_TABLE_STANDARD_FIELDS if(current_next_indicator == 0) return; int transport_stream_id = table_id_extension; int original_network_id = p[0]<<8|p[1]; /* * We can use the SDT_ACTUAL table to populate our useful tuner information... */ if(table_id == TABLE_ID_SDT_ACTUAL) { packet->tuner->current_original_network_id = original_network_id; packet->tuner->current_transport_stream_id = transport_stream_id; } if(packet->tuner->scope == TUNER_SCOPE_SATELLITE && packet->pid == DVB_TABLE_SDT) { /* * We don't care about these as it's standard Astra stuff, however we do need to make sure * we've seen the SDT_ACTUAL so we can set the tuner values above before we clear out the * stream */ // if(table_id == TABLE_ID_SDT_ACTUAL) core_input_destroy_stream(tuner->input_context, packet->id); if(table_id == TABLE_ID_SDT_ACTUAL) tuner_cmd(packet->tuner, TUNER_CMD_REMOVE_SI_PID, packet->pid); return; } p+=3; while( p < end ) { int service_id = p[0]<<8|p[1]; /* * Now we can see if we already have a program structure for this service_id/on_id, and if not we * create a new one... */ struct si_program *program = find_or_create_si_program(service_id, transport_stream_id, original_network_id); // fprintf(stderr, "Created program with service id=%d onid=%d\n", service_id, original_network_id); if(!program) return; /* * Now we can populate the core data... */ // TODO: since we are combining TS info, we need to be a little cleverer about how // we use the eit flags here.. Perhaps we need a flag to show if we have a tuner // looking at this TS. program->eit_schedule_flag = (p[2]&0x02)>>1; program->eit_present_following_flag = p[2]&0x01; program->running_status = (p[3]&0xe0)>>5; program->free_ca_mode = (p[3]&0x10)>>4; program->have_sdt = 1; program->transport_stream_id = transport_stream_id; p += 3; INIT_DESCRIPTORS(p) DESCRIPTOR(SERVICE_DESC) { program->service_type = p[0]; strncpyz(program->service_provider, p+2, p[1]); strncpyz(program->service_name, p+3+p[1], p[p[1]+2]); // fprintf(stderr, "SDT ONID=0x%04x/%d SERVID=0x%4x/%d [%s] (tsid=%d)\n", original_network_id, original_network_id, // service_id, service_id, program->service_name, transport_stream_id); } DESCRIPTOR(TIME_SHIFED_SERVICE) { /* * We get this instead of SERVICE_DESC for services that are time shifted copies of others (NVOD) */ //uint16_t reference_service = (p[0]<<8)|p[1]; } DESCRIPTOR(NVOD_REFERENCE) { /* Don't this we need to do anything with this at the current time, currently only really used * for sky box office by the looks of it */ } PRIV_DESCRIPTOR(BBC, FREESAT_D9) { /* * This seems to be a language name followed by a short service name?? */ char lang[4]; char short_name[MAX_SI_STRING]; strncpyz(lang, p, 3); strncpyz(short_name, p+4, p[3]); fprintf(stderr, "SDT(0x%04x/%d) FREESAT-D9: [%s] [%s]\n", service_id, service_id, lang, short_name); } PRIV_DESCRIPTOR(SKY, TODO_C0) { /* * This seems to be a channel name, but looks like it's only present on the sky channels */ char string[MAX_SI_STRING]; strncpyz(string, p, length); fprintf(stderr, "TODO_C0: [%s] (program_id=%d)\n", string, service_id ); } PRIV_DESCRIPTOR(SKY, TODO_B2) { /* * Seems to be a Sky private data thing ... not sure what info is in there at this stage */ } DESCRIPTOR(DEFAULT_AUTHORITY) { strncpyz(program->authority, p, length); } DESCRIPTOR(COUNTRY_AVAILABILITY) { int for_these_countries = (p[0]&0x80)>>7; char country[4]; country[3] = '\0'; uint8_t *d = p+1; while(d < p + length) { country[0] = d[0]; country[1] = d[1]; country[2] = d[2]; // fprintf(stderr, "COUNTRY AVAILABILITY: (%d) -> %s\n", for_these_countries, country); d += 3; } } DESCRIPTOR(DATA_BROADCAST) { program->data_broadcast_id = p[0]<<8|p[1]; program->data_component_tag = p[2]; program->data_selector_length = p[3]; strncpy((char *)program->data_selector, (char *)p+4, p[3]); strncpyz(program->data_language, p+4+p[3], 3); strncpyz(program->data_description, p+8+p[3], p[p[3]+7]); } DESCRIPTOR(CA_IDENT) { program->ca_system_id = p[0]<<8|p[1]; if(length != 2) { fprintf(stderr, "ERROR::: MULTIPLE CA idents received\n" ); } } END_DESCRIPTORS(p) unlock_si_program(program); } return; } /*---------------------------------------------------------------------------------------- * The NIT gives us a set of transport streams, so we'll record these and also we get the * Freeview LCN's (if we're tuned to freeview) *---------------------------------------------------------------------------------------- */ void process_nit(struct si_data_packet *packet) { SI_TABLE_STANDARD_FIELDS /* * Make sure we haven't seen it already, then pull the record out of the database (or put a new * one in, once we've got the name) ... then we go on to process the transport stream info. */ int network_id = table_id_extension; /* * OK, now we process the table... */ INIT_DESCRIPTORS(p) DESCRIPTOR(NETWORK_NAME) { char name[MAX_SI_STRING]; strncpyz(name, p, length); fprintf(stderr, "NETWORK NAME %04x: [%s]\n", network_id, name ); } // TODO: potential DEFAULT_AUTHORITY here DESCRIPTOR(LINKAGE_DESC) { int tsid = p[0]<<8|p[1]; int onid = p[2]<<8|p[3]; int sid = p[4]<<8|p[5]; int linkage_type = p[6]; fprintf(stderr, "LINKAGE tsid=%d onid=%d sid=%d type=%d\n", tsid, onid, sid, linkage_type); } END_DESCRIPTORS(p) p+=2; while(p < end) { int transport_stream_id = p[0]<<8|p[1]; int original_network_id = p[2]<<8|p[3]; struct si_transport_stream *transport_stream = find_or_create_si_transport_stream(transport_stream_id, original_network_id); if(!transport_stream) return; p+=4; INIT_DESCRIPTORS(p) DESCRIPTOR(SERVICE_LIST) { /* We get this from the PMT generally, so ignoring it from here: TODO: ?? */ DP_ERROR("TODO: NIT -- we have a service list here????"); } // TODO: potential DEFAULT_AUTHORITY here too ... (per TS) DESCRIPTOR(TERRESTRIAL_DELIV) { /* * We can get the info for other tuning sources from here, so useful to aid speedy service discovery */ transport_stream->delivery.terrestrial.centre_frequency = p[0]<<24|p[1]<<16|p[2]<<8|p[3]; transport_stream->delivery.terrestrial.bandwidth = (p[4]&0xe0)>>5; transport_stream->delivery.terrestrial.priority = (p[4]&0x10)>>4; transport_stream->delivery.terrestrial.time_slicing_indicator = (p[4]&0x08)>>3; transport_stream->delivery.terrestrial.mpe_fec_indicator = (p[4]&0x04)>>2; transport_stream->delivery.terrestrial.constellation = (p[5]&0xc0)>>6; transport_stream->delivery.terrestrial.hierarchy_information = (p[5]&0x38)>>3; transport_stream->delivery.terrestrial.code_rate_hp_stream = p[5]&0x07; transport_stream->delivery.terrestrial.code_rate_lp_stream = (p[6]&0xe0)>>5; transport_stream->delivery.terrestrial.guard_interval = (p[6]&0x18)>>3; transport_stream->delivery.terrestrial.transmission_mode = (p[6]&0x06)>>1; transport_stream->delivery_type = DELIVERY_TYPE_DVB_T; } DESCRIPTOR(SATELLITE_DELIVERY) { // TODO ... we need this to identify tuning targets etc. #define BCD(x) (((((x)&0xf0)>>4)*10)+((x)&0x0f)) int frequency = BCD(p[0])*1000000 + BCD(p[1])*10000 + BCD(p[2])*100 + BCD(p[3]); // fprintf(stderr, "FREQUENCY: %d\n", frequency); int west_east_flag = (p[6]&0x80)>>7; int polarization = (p[6]&0x60)>>5; int modulation_system = (p[6]&0x04)>>3; int modulation_type = (p[6]&0x03); int symbol_rate = BCD(p[7])*100000 + BCD(p[8])*1000 + BCD(p[9])*10 + BCD((p[10]&0xf0)>>4); /* * This puts the symbol rate and freq back into the normal form used by the tuning code */ symbol_rate /= 10; frequency /= 100; DP_ERROR("west_east=%d polar=%d modsys=%d mod_type=%d sr=%d\n", west_east_flag, polarization, modulation_system, modulation_type, symbol_rate); transport_stream->delivery.satellite.frequency = frequency; transport_stream->delivery.satellite.symbol_rate = symbol_rate; if(polarization == 0) { transport_stream->delivery.satellite.polarity = POLARITY_HORIZONTAL; } else if(polarization == 1) { transport_stream->delivery.satellite.polarity = POLARITY_VERTICAL; } else { DP_ERROR("unrecognised polarity %d", polarization); } transport_stream->delivery_type = DELIVERY_TYPE_DVB_S; } DESCRIPTOR(LOGICAL_CHANNEL) { /* * Here we can pull out the FreeView logical channel numbers */ uint8_t *d = p; while(d < p + length) { int service_id = d[0]<<8|d[1]; struct si_program *program = find_or_create_si_program(service_id, transport_stream_id, original_network_id); if(!program) return; program->visible_service_flag = (d[2]&&0x80)>>7; program->logical_channel_number = (d[2]&0x03)<<8|d[3]; // fprintf(stderr, "LCN (%04x) P%04x: %d\n", transport_stream_id, program->program_id, program->logical_channel_number); unlock_si_program(program); d += 4; } } DESCRIPTOR(FREQ_LIST) { /* we don't need to use this */ } END_DESCRIPTORS(p) } return; } /*---------------------------------------------------------------------------------------- * The EIT provides the data for the EPG on a variety of tables depending on the content * and whether it's for our transport stream or not. * * We normally always return 1 from this so we don't see the same data twice, however if * this would trigger a recording event and we don't have enough information about the * program then we'll return 0 to make sure we get the event again. *---------------------------------------------------------------------------------------- */ int process_eit(struct si_data_packet *packet) { SI_TABLE_STANDARD_FIELDS int service_id = table_id_extension; int transport_stream_id = p[0]<<8|p[1]; int original_network_id = p[2]<<8|p[3]; /* * Now we may be able to use this information to help the tuner out... */ if(!packet->tuner->current_transport_stream_id || !packet->tuner->current_original_network_id) { if(table_id == TABLE_ID_EIT_PF_ACTUAL || (table_id >= TABLE_ID_EIT_ACTUAL_START && table_id <= TABLE_ID_EIT_ACTUAL_END)) { packet->tuner->current_transport_stream_id = transport_stream_id; packet->tuner->current_original_network_id = original_network_id; } } /* * We'll only be listening to the relevant EIT's, so only a little bit of juggling to do in renaming * the Freesat table info for the ACTUAL stream. */ if(packet->tuner->current_transport_stream_id == transport_stream_id && packet->tuner->current_original_network_id == original_network_id) { if(table_id == TABLE_ID_EIT_PF_FREESAT) { table_id = TABLE_ID_EIT_PF_ACTUAL; } else if(table_id == TABLE_ID_EIT_PF_OTHER) { table_id = TABLE_ID_EIT_PF_ACTUAL; } } else if(table_id == TABLE_ID_EIT_PF_FREESAT) { table_id = TABLE_ID_EIT_PF_OTHER; } struct si_program *program = find_or_create_si_program(service_id, transport_stream_id, original_network_id); if(!program) return 1; //fprintf(stderr, "Program:: id=%d tsid=%d onid=%d\n", program->program_id, program->transport_stream_id, program->original_network_id); /* * Now work out where we are going to store this... * TODO: handle the NVOD potential issue here... or is it ok like this? */ struct si_now_next_event *now_next = NULL; if(table_id == TABLE_ID_EIT_PF_ACTUAL || table_id == TABLE_ID_EIT_PF_OTHER) { if(section_number == 0) { now_next = &program->now_event; } else if(section_number == 1) { now_next = &program->next_event; } } /* * If we are filling in a now/next, there is the possibility of no current event so * we'll set the event_id to 0 to signify this... */ if(now_next) now_next->event_id = 0; p += 6; while( p < end ) { int event_id = p[0]<<8|p[1]; /* * If this is a now/next event then we want to store it in the channel structure. * * From the standard we know that we should see only two events, they seem to be in different sections * so we are only actually interested in the first one in the first section. * * TODO: see standard for NVOD reference and more than two events! How do we discard? */ struct si_event *event = NULL; if(now_next) { now_next->event_id = event_id; now_next->start_time = dvb_time_to_time(p+2); now_next->duration = p[7]<<16|p[8]<<8|p[9]; now_next->running_status = (p[10]&0xe0)>>5; now_next->free_ca_mode = (p[10]&0x10)>>4; now_next->component_count = 0; } else { event = find_or_create_si_event(&program->events, event_id); if(!event) continue; event->start_time = dvb_time_to_time(p+2); event->duration = p[7]<<16|p[8]<<8|p[9]; event->running_status = (p[10]&0xe0)>>5; event->free_ca_mode = (p[10]&0x10)>>4; } p += 10; INIT_DESCRIPTORS(p) DESCRIPTOR(CONTENT_DESC) { // TODO: these are the tags that describe what the program is } DESCRIPTOR(CA_IDENT) { // TODO: for CA support } #define is_media_content(c) \ (c == DVB_STREAM_CONTENT_MPEG_AUDIO || c == DVB_STREAM_CONTENT_MPEG_VIDEO || c == DVB_STREAM_CONTENT_SUBTITLES || \ c == DVB_STREAM_CONTENT_AAC_AUDIO || c == DVB_STREAM_CONTENT_AC3_AUDIO || c == DVB_STREAM_CONTENT_H264_VIDEO) DESCRIPTOR(COMPONENT_DESC) { int stream_content = p[0]&0x0f; int component_type = p[1]; int component_tag = p[2]; int desc_string_len = length-6; if(is_media_content(stream_content)) { if(now_next) { now_next->components[now_next->component_count].content = stream_content; now_next->components[now_next->component_count].type = component_type; now_next->components[now_next->component_count].tag = component_tag; strncpyz(now_next->components[now_next->component_count].language, p+3, 3); if(desc_string_len > 0) strncpyz(now_next->components[now_next->component_count].description, p+6, desc_string_len); now_next->component_count++; //fprintf(stderr, "EVENT component %04x (count=%d) -- content=%d type=%d tag=%d\n", event_id, now_next->component_count, stream_content, // component_type, component_tag); } else { // TODO: work out flags //fprintf(stderr, "EVENT component %04x -- content=%d type=%d tag=%d\n", event_id, stream_content, // component_type, component_tag); } } } DESCRIPTOR(SHORT_EVENT) { int name_len = p[3]; int desc_len = p[4+p[3]]; char *dest_language = (now_next ? now_next->language : event->language); char *dest_name = (now_next ? now_next->name : event->name); char *dest_description = (now_next ? now_next->description : event->description); strncpyz(dest_language, p, 3); /* * Handle freesat huffman decoding... */ if(*(p+4) == 0x1f) { // freesat_huffman_decode(p+4, name_len, dest_name, MAX_SI_STRING); // DP_ERROR("huffman name: %s", dest_name); } else { strncpyz(dest_name, p+4, name_len); } if(*(p+5+name_len) == 0x1f) { // freesat_huffman_decode(p+5+name_len, desc_len, dest_description, MAX_SI_STRING); // DP_ERROR("huffman desc: %s", dest_description); } else { strncpyz(dest_description, p+5+name_len, desc_len); } } /* * This is the CRID concept used for series-link type information... */ DESCRIPTOR(CONTENT_IDENTIFIER) { uint8_t *d = p; while(d < p + length) { int crid_type = (d[0]&0xfc)>>2; int crid_location = d[0]&0x03; if(crid_location == 0) { /* * It's in the descriptor, so we now need to build the fully qualified version.. */ int crid_length = d[1]; char tmp[255]; // TODO: if not here, then look in TS and then network char *default_authority = program->authority; if(d[2] == '/') { // Short form... strcpy(tmp, default_authority); strncat(tmp, (char *)&d[2], crid_length); } else { strncpy(tmp, (char *)&d[2], crid_length); } struct si_crid *cref = crid_find(tmp); if(!cref) { DP_ERROR("unable to store crid in table: %s", tmp); } else { if(crid_type == 0x01 || crid_type == 0x31) { if(now_next) { now_next->program_crid = cref; } else { event->program_crid = cref; } } else if(crid_type == 0x02 || crid_type == 0x32) { if(now_next) { now_next->series_crid = cref; } else { event->series_crid = cref; } } else { DP_ERROR("unsupported CRID type: %d", crid_type); } } char *evn = (now_next ? now_next->name : event->name); // fprintf(stderr, "CRID: event [%s] crid [%s] type %d [%02x]\n", evn, tmp, crid_type, d[0]); d += 1 + 1 + crid_length; } else { // In a Content Identifier Table uint16_t crid_ref = d[1]<<8|d[2]; DP_ERROR("External CRID found: ref=%04x", crid_ref); d += 1 + 2; } } } END_DESCRIPTORS(p) } /* * If any of our programs (in our current transport stream) have a change of now_event * we need to send a message to the tuner_manager to make sure it knows what's going on * * Basically we'll just send the message for any now event that's changing in the current stream... */ int rc = 1; if(table_id == TABLE_ID_EIT_PF_ACTUAL && section_number == 0) { /* * In order for us to be able to record properly we need to have the PMT so we have the * stream information. * TODO: do we need the SDT so we have channel details or could we add that in later? */ if(!program->have_pmt) { rc = 0; } else { // fprintf(stderr, "sending record message programid=%d onid=%d eventid=%d\n", program->program_id, // program->original_network_id, program->now_event.event_id); // struct control_message *control_message = new_control_message(packet->tuner, TUNER_REQUEST_EVENT_CHANGE, 0); // if(control_message) { // control_message->args.event_change.event_id = program->now_event.event_id; // control_message->args.event_change.program_id = program->program_id; // control_message->args.event_change.transport_stream_id = program->transport_stream_id; // control_message->args.event_change.original_network_id = program->original_network_id; // send_tuner_manager_message(control_message); // } tuner_mgr_cmd(packet->tuner, TUNER_REQUEST_EVENT_CHANGE, program->now_event.event_id, program->program_id, program->transport_stream_id, program->original_network_id); } } unlock_si_program(program); return rc; } /*---------------------------------------------------------------------------------------- * This is just the date and time table, this gives us the UTC time according to the * data stream ... we'll ultimately use this to set and sync the system time. *---------------------------------------------------------------------------------------- */ int process_tdt(struct tuner_details *tuner, uint8_t *p, uint8_t *end) { SI_TABLE_SHORT_FIELDS int mjd = p[3]<<8|p[4]; int bcd = p[5]<<16|p[6]<<8|p[7]; fprintf(stderr, "DATE AND TIME: %04x %06x\n", mjd, bcd ); return 1; } /*---------------------------------------------------------------------------------------- * This is the main state engine that handles incoming tables and works out what to do * with them. With the multi-network setup this can be quite complex, so we track the * situation for each network and do the appropriate thing when we get a new table. *---------------------------------------------------------------------------------------- */ #define find_si_table(in_id, in_ext, in_pid) core_data_hash_item_find(&si_tables, CORE_DATA_HASHi3(in_id, in_ext, in_pid), \ struct si_table, \ item->table_id == in_id && \ item->table_id_extension == in_ext && \ item->pid == in_pid); int si_engine(struct si_table *table, struct si_data_packet *packet) { switch (table->table_id) { /* * We can just process the PAT to make sure our PMT's get added to the filters */ case TABLE_ID_PAT: process_pat(packet); return 1; /* * The PMT's we can process once we have the tuner transport_stream_id and original_network_id * otherwise we return 0 to make sure we keep seeing them. */ case TABLE_ID_PMT: if(packet->tuner->current_transport_stream_id && packet->tuner->current_original_network_id) { process_pmt(packet); return 1; } return 0; /* * The SDT we'll process straight away, if it's the standard one then we should * check to see if we need to then process the PAT */ case TABLE_ID_SDT_ACTUAL: case TABLE_ID_SDT_OTHER: process_sdt(packet); return 1; /* * The NIT we can just process, this will fill in some of our transport stream database but will * also give us Freeview LCN's. */ case TABLE_ID_NIT_ACTUAL: case TABLE_ID_NIT_OTHER: fprintf(stderr, "Processing the NIT -- should only do this once\n"); process_nit(packet); return 1; /* * The BAT is only really of use in the Freesat case, although we could populate TS info, since * we get all the info we need within the BAT itself we can just process away */ case TABLE_ID_BAT: process_bat(packet); return 1; /* * The EIT is a self contained set of data so we could just process it, however it may * trigger a recording so we need to make sure we have enough information about the program. * We'll do this within the process function and then return 0 if we need the event again. */ case TABLE_ID_EIT_PF_ACTUAL: case TABLE_ID_EIT_PF_OTHER: case TABLE_ID_EIT_PF_FREESAT: return process_eit(packet); case TABLE_ID_EIT_ACTUAL_START ... TABLE_ID_EIT_ACTUAL_END: case TABLE_ID_EIT_OTHER_START ... TABLE_ID_EIT_OTHER_END: //fprintf(stderr, "ignoring eit for now\n"); return 1; case TABLE_ID_DSMCC_UN: process_dsmcc_un(packet); return 1; case TABLE_ID_DSMCC_DOWNLOAD: process_dsmcc_download(packet); return 1; default: fprintf(stderr, "Unhandled table: %d\n", table->table_id); return 1; } } /*---------------------------------------------------------------------------------------- * Here we process incoming SI table information, keeping track of version changes and * eventually, when we get a complete table we call the si_table_state_engine to see * what we need to do. * * We take the input_context because we need to set and use the current_transport field for * the tuner, plus we need to control the streams to get the PMT's. *---------------------------------------------------------------------------------------- */ void *si_processing_thread(void *ptr) { while(1) { // dp_data_packet *packet = dp_queue_shift(&si_incoming_queue); struct si_data_packet *packet = core_data_list_shiftwait(&si_incoming_queue); ASSERT(packet, "got null packet, aborting", return NULL); SI_TABLE_STANDARD_FIELDS // fprintf(stderr, "Looking at tableid %d (pid=%d extid=%d section=%d)\n", table_id, packet->id, table_id_extension, section_number); if(current_next_indicator == 0) { free(packet); continue; } // struct tuner_details *tuner = (struct tuner_details *)((dp_input_context *)packet->opaque)->implementation; /* * First we find our table structure, and create one if we don't already have one... */ struct si_table *table = find_si_table(table_id, table_id_extension, packet->pid); if(!table) { table = malloc(sizeof(struct si_table)); if(!table) { DP_ERROR("unable to malloc for si_table"); free(packet); continue; } table->table_id = table_id; table->table_id_extension = table_id_extension; table->pid = packet->pid; memset(table->sections, 0xff, sizeof(table->sections)); table->working_version_number = -1; table->seen_sections = 0; table->last_section_number = -1; table->last_complete_version = -1; core_data_hash_add_item(&si_tables, table, CORE_DATA_HASHi3(table_id, table_id_extension, packet->pid)); } /* * The most likely thing is that this is a repeat of something we've already seen and * processed... */ if(table->sections[section_number] == version_number) { free(packet); continue; } /* * So we must need this section, could either be the version we want, or potentially the start of a * new version */ if(version_number != table->working_version_number) { /* * A new version coming in, we will either be part way through collecting the prior * version, or we'll be done collecting it -- doesn't really matter. */ table->seen_sections = 0; table->last_section_number = last_section_number; table->working_version_number = version_number; } /* * At this point we have a new section of a sub-table, but for some things there is a specific * order we need, so we'll call the processing engine, but if it returns a zero it effectively * means that we couldn't do anything with it, so treat it as unseen .. we'll need it again later. */ if(si_engine(table, packet)) { table->sections[section_number] = version_number; table->seen_sections++; if(table->seen_sections > table->last_section_number) table->last_complete_version = version_number; } free(packet); } /* return; struct tuner_details *tuner = (struct tuner_details *)input_context->implementation; if(table_id == TABLE_ID_PAT) { process_pat(tuner, p, end); } else if(table_id == TABLE_ID_BAT) { process_bat(tuner, packet->id, p, end); } else if(table_id == TABLE_ID_PMT) { process_pmt(tuner, p, end); } else if(table_id == TABLE_ID_SDT_ACTUAL || table_id == 0x46) { process_sdt(tuner, packet->id, p, end); } else if(table_id == 0x70) { process_tdt(tuner, p, end); } else if(table_id == TABLE_ID_NIT_ACTUAL || table_id == TABLE_ID_NIT_OTHER ) { fprintf(stderr, "NIT (%02x) -- pid=%d\n", table_id, packet->id ); process_nit(tuner, packet->id, p, end); } else if(table_id == TABLE_ID_EIT_PF_ACTUAL || table_id == TABLE_ID_EIT_PF_OTHER || table_id == TABLE_ID_EIT_PF_FREESAT || (table_id >= TABLE_ID_EIT_ACTUAL_START && table_id <= TABLE_ID_EIT_ACTUAL_END) ) { process_eit(tuner, p, end); } else { DP_ERROR("unhandled table, id=%02x", table_id); } */ return NULL; } /*---------------------------------------------------------------------------------------- * Utility function to push SI packets onto the queue, called externally *---------------------------------------------------------------------------------------- */ //void push_si_packet(dp_input_context *input_context, dp_data_packet *packet) { // packet->opaque = (void *)input_context; // dp_queue_push(&si_incoming_queue, packet, packet->size); //} void push_si_packet(struct si_data_packet *packet) { core_data_list_lock(&si_incoming_queue); core_data_list_add_item(&si_incoming_queue, packet); core_data_list_signal(&si_incoming_queue); core_data_list_unlock(&si_incoming_queue); } /*---------------------------------------------------------------------------------------- * Initialise the SI tables mechanism *---------------------------------------------------------------------------------------- */ int core_si_init() { core_data_init_hash(&si_tables, NULL); core_data_init_hash(&si_programs, free_si_program); pthread_mutex_init(&si_programs_lock, NULL); core_data_init_hash(&si_crids, NULL); pthread_mutex_init(&si_crids_lock, NULL); core_data_init_hash(&si_transports, NULL); // dp_queue_init(&si_incoming_queue, DP_QUEUE_BLOCKING, 0, core_input_data_packet_free); core_data_init_list(&si_incoming_queue, NULL); core_data_list_init_lock(&si_incoming_queue); core_data_list_init_cond(&si_incoming_queue); pthread_create( &si_incoming_thread, NULL, si_processing_thread, (void *)NULL ); return 1; }