/*
* 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;
}