/* * Copyright (C) 2000-2004 the xine project * * This file is part of xine, a free video player. * * xine is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * xine is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * * input plugin for Digital TV (Digital Video Broadcast - DVB) devices * e.g. Hauppauge WinTV Nova supported by DVB drivers from Convergence * * DONE: * - Use EIT info for current/next programming OSD * - Pause recording * - allow user to select card to use, rather than hard-code to adapter0 * - * TODO/Wishlist: (not in any order) * - Parse all Administrative PIDs - NIT,SDT,CAT etc * - As per James' suggestion, we need a way for the demuxer * to request PIDs from the input plugin. * - Timeshift ability. * - Pipe teletext infomation to a named fifo so programs such as * Alevtd can read it. * - Allow the user to view one set of PIDs (channel) while * recording another on the same transponder - this will require either remuxing or * perhaps bypassing the TS demuxer completely - we could easily have access to the * individual audio/video streams via seperate read calls, so send them to the decoders * and save the TS output to disk instead of passing it to the demuxer. * This also gives us full control over the streams being played..hmm..control... * - Parse and use full EIT for programming info. * - Allow the user to find and tune new stations from within xine, and * do away with the need for dvbscan & channels.conf file. * - Enable use of Conditional Access devices for scrambled content. * - if multiple cards are available, optionally use these to record/gather si info, * and leave primary card for viewing. * - allow for handing off of EPG data to specialised frontends, instead of displaying via * OSD - this will allow for filtering/searching of epg data - useful for automatic recording :) * - Parse EPG info directly from TS to allow for constant updates, or read from a separate thread. * Activating EPG will currently block the calling thread, which may cause issues with recording. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #ifdef __sun #include #endif #include #include #include #include /* These will eventually be #include */ #include "dvb/dmx.h" #include "dvb/frontend.h" #define LOG_MODULE "input_dvb" #define LOG_VERBOSE /* #define LOG */ #include "xine_internal.h" #include "xineutils.h" #include "input_plugin.h" #include "net_buf_ctrl.h" /* comment this out to have audio-only streams in the menu as well */ /* workaround for xine's unability to handle audio-only ts streams */ #define FILTER_RADIO_STREAMS #define BUFSIZE 16384 #define NOPID 0xffff /* define stream types * administrative/system PIDs first */ #define INTERNAL_FILTER 0 #define PATFILTER 1 #define PMTFILTER 2 #define EITFILTER 3 #define PCRFILTER 4 #define VIDFILTER 5 #define AUDFILTER 6 #define AC3FILTER 7 #define TXTFILTER 8 #define MAXFILTERS 9 #define MAX_AUTOCHANNELS 200 #define MAX_SUBTITLES 4 /* Mouse button codes. */ #define MOUSE_BUTTON_LEFT 1 #define MOUSE_BUTTON_MIDDLE 2 #define MOUSE_BUTTON_RIGHT 3 #define MOUSE_WHEEL_UP 4 #define MOUSE_WHEEL_DOWN 5 /* The "thumb button" of my Intellimouse. */ #define MOUSE_SIDE_LEFT 6 #define MOUSE_SIDE_RIGHT 7 /* EPG settings. */ /* How many chars (max) of the program name to display. */ #define MAX_PROGRAM_NAME_LENGTH 32 #define bcdtoint(i) ((((i & 0xf0) >> 4) * 10) + (i & 0x0f)) typedef struct { int fd_frontend; int fd_pidfilter[MAXFILTERS]; int fd_subfilter[MAX_SUBTITLES]; struct dvb_frontend_info feinfo; int adapter_num; char frontend_device[100]; char dvr_device[100]; char demux_device[100]; struct dmx_pes_filter_params pesFilterParams[MAXFILTERS]; struct dmx_pes_filter_params subFilterParams[MAX_SUBTITLES]; struct dmx_sct_filter_params sectFilterParams[MAXFILTERS]; xine_t *xine; } tuner_t; typedef struct { /* EIT Information */ char *progname; char *description; char *starttime; char *duration; char *content; int rating; } eit_info_t; typedef struct { char *name; struct dvb_frontend_parameters front_param; int pid[MAXFILTERS]; int subpid[MAX_SUBTITLES]; int service_id; int sat_no; int tone; int pol; int pmtpid; eit_info_t eit[2]; } channel_t; typedef struct { input_class_t input_class; xine_t *xine; char *mrls[5]; int numchannels; char *autoplaylist[MAX_AUTOCHANNELS]; } dvb_input_class_t; typedef struct { input_plugin_t input_plugin; dvb_input_class_t *class; xine_stream_t *stream; char *mrl; off_t curpos; nbc_t *nbc; tuner_t *tuner; channel_t *channels; int fd; int num_channels; int channel; pthread_mutex_t mutex; osd_object_t *osd; osd_object_t *rec_osd; osd_object_t *name_osd; osd_object_t *paused_osd; osd_object_t *proginfo_osd; osd_object_t *channel_osd; osd_object_t *background; xine_event_queue_t *event_queue; /* CRC table for PAT rebuilding */ unsigned long crc32_table[256]; /* scratch buffer for forward seeking */ char seek_buf[BUFSIZE]; /* simple vcr-like functionality */ int record_fd; int record_paused; /* centre cutout zoom */ int zoom_ok; /* display channel name */ int displaying; /* buffer for EIT data */ char *eitbuffer; int num_streams_in_this_ts; /* number of timedout reads in plugin_read */ int read_failcount; } dvb_input_plugin_t; typedef struct { char *name; int value; } Param; static const Param inversion_list [] = { { "INVERSION_OFF", INVERSION_OFF }, { "INVERSION_ON", INVERSION_ON }, { "INVERSION_AUTO", INVERSION_AUTO }, { NULL, 0 } }; static const Param bw_list [] = { { "BANDWIDTH_6_MHZ", BANDWIDTH_6_MHZ }, { "BANDWIDTH_7_MHZ", BANDWIDTH_7_MHZ }, { "BANDWIDTH_8_MHZ", BANDWIDTH_8_MHZ }, { NULL, 0 } }; static const Param fec_list [] = { { "FEC_1_2", FEC_1_2 }, { "FEC_2_3", FEC_2_3 }, { "FEC_3_4", FEC_3_4 }, { "FEC_4_5", FEC_4_5 }, { "FEC_5_6", FEC_5_6 }, { "FEC_6_7", FEC_6_7 }, { "FEC_7_8", FEC_7_8 }, { "FEC_8_9", FEC_8_9 }, { "FEC_AUTO", FEC_AUTO }, { "FEC_NONE", FEC_NONE }, { NULL, 0 } }; static const Param guard_list [] = { {"GUARD_INTERVAL_1_16", GUARD_INTERVAL_1_16}, {"GUARD_INTERVAL_1_32", GUARD_INTERVAL_1_32}, {"GUARD_INTERVAL_1_4", GUARD_INTERVAL_1_4}, {"GUARD_INTERVAL_1_8", GUARD_INTERVAL_1_8}, { NULL, 0 } }; static const Param hierarchy_list [] = { { "HIERARCHY_1", HIERARCHY_1 }, { "HIERARCHY_2", HIERARCHY_2 }, { "HIERARCHY_4", HIERARCHY_4 }, { "HIERARCHY_NONE", HIERARCHY_NONE }, { NULL, 0 } }; static const Param qam_list [] = { { "QPSK", QPSK }, { "QAM_128", QAM_128 }, { "QAM_16", QAM_16 }, { "QAM_256", QAM_256 }, { "QAM_32", QAM_32 }, { "QAM_64", QAM_64 }, { NULL, 0 } }; static const Param transmissionmode_list [] = { { "TRANSMISSION_MODE_2K", TRANSMISSION_MODE_2K }, { "TRANSMISSION_MODE_8K", TRANSMISSION_MODE_8K }, { NULL, 0 } }; struct tm * dvb_mjdtime (char *buf); static void do_eit(dvb_input_plugin_t *this); /* Utility Functions */ static void ts_build_crc32_table(dvb_input_plugin_t *this) { uint32_t i, j, k; for( i = 0 ; i < 256 ; i++ ) { k = 0; for (j = (i << 24) | 0x800000 ; j != 0x80000000 ; j <<= 1) { k = (k << 1) ^ (((k ^ j) & 0x80000000) ? 0x04c11db7 : 0); } this->crc32_table[i] = k; } } static uint32_t ts_compute_crc32(dvb_input_plugin_t *this, uint8_t *data, uint32_t length, uint32_t crc32) { uint32_t i; for(i = 0; i < length; i++) { crc32 = (crc32 << 8) ^ this->crc32_table[(crc32 >> 24) ^ data[i]]; } return crc32; } static unsigned int getbits(unsigned char *buffer, unsigned int bitpos, unsigned int bitcount) { unsigned int i; unsigned int val = 0; for (i = bitpos; i < bitcount + bitpos; i++) { val = val << 1; val = val + ((buffer[i >> 3] & (0x80 >> (i & 7))) ? 1 : 0); } return val; } static int find_descriptor(uint8_t tag, const unsigned char *buf, int descriptors_loop_len, const unsigned char **desc, int *desc_len) { while (descriptors_loop_len > 0) { unsigned char descriptor_tag = buf[0]; unsigned char descriptor_len = buf[1] + 2; if (!descriptor_len) { break; } if (tag == descriptor_tag) { if (desc) *desc = buf; if (desc_len) *desc_len = descriptor_len; return 1; } buf += descriptor_len; descriptors_loop_len -= descriptor_len; } return 0; } /* extract UTC time and date encoded in modified julian date format and return pointer to tm * in localtime */ struct tm * dvb_mjdtime (char *buf) { int i; unsigned int year, month, day, hour, min, sec; unsigned long int mjd; struct tm *tma = xine_xmalloc(sizeof(struct tm)); struct tm *dvb_time; time_t t; mjd = (unsigned int)(buf[0] & 0xff) << 8; mjd +=(unsigned int)(buf[1] & 0xff); hour =(unsigned char)bcdtoint(buf[2] & 0xff); min = (unsigned char)bcdtoint(buf[3] & 0xff); sec = (unsigned char)bcdtoint(buf[4] & 0xff); year =(unsigned long)((mjd - 15078.2)/365.25); month=(unsigned long)((mjd - 14956.1 - (unsigned long)(year * 365.25))/30.6001); day = mjd - 14956 - (unsigned long)(year * 365.25) - (unsigned long)(month * 30.6001); if (month == 14 || month == 15) i = 1; else i = 0; year += i; month = month - 1 - i * 12; tma->tm_sec=sec; tma->tm_min=min; tma->tm_hour=hour; tma->tm_mday=day; tma->tm_mon=month-1; tma->tm_year=year; t=timegm(tma); dvb_time=localtime(&t); free(tma); return dvb_time; } static void tuner_dispose(tuner_t * this) { int x; if (this->fd_frontend >= 0) close(this->fd_frontend); /* close all pid filter filedescriptors */ for (x = 0; x < MAXFILTERS; x++) if (this->fd_pidfilter[x] >= 0) close(this->fd_pidfilter[x]); /* close all pid filter filedescriptors */ for (x = 0; x < MAX_SUBTITLES; x++) if (this->fd_subfilter[x] >= 0) close(this->fd_subfilter[x]); if(this) free(this); } static tuner_t *tuner_init(xine_t * xine, int adapter) { tuner_t *this; int x; int test_video; char *video_device=xine_xmalloc(200); this = (tuner_t *) xine_xmalloc(sizeof(tuner_t)); this->fd_frontend = -1; for (x = 0; x < MAXFILTERS; x++) this->fd_pidfilter[x] = 0; this->xine = xine; this->adapter_num = adapter; snprintf(this->frontend_device,100,"/dev/dvb/adapter%i/frontend0",this->adapter_num); snprintf(this->demux_device,100,"/dev/dvb/adapter%i/demux0",this->adapter_num); snprintf(this->dvr_device,100,"/dev/dvb/adapter%i/dvr0",this->adapter_num); snprintf(video_device,100,"/dev/dvb/adapter%i/video0",this->adapter_num); if ((this->fd_frontend = open(this->frontend_device, O_RDWR)) < 0) { xprintf(this->xine, XINE_VERBOSITY_DEBUG, "FRONTEND DEVICE: %s\n", strerror(errno)); tuner_dispose(this); return NULL; } if ((ioctl(this->fd_frontend, FE_GET_INFO, &this->feinfo)) < 0) { xprintf(this->xine, XINE_VERBOSITY_DEBUG, "FE_GET_INFO: %s\n", strerror(errno)); tuner_dispose(this); return NULL; } for (x = 0; x < MAXFILTERS; x++) { this->fd_pidfilter[x] = open(this->demux_device, O_RDWR); if (this->fd_pidfilter[x] < 0) { xprintf(this->xine, XINE_VERBOSITY_DEBUG, "DEMUX DEVICE PIDfilter: %s\n", strerror(errno)); tuner_dispose(this); return NULL; } } for (x = 0; x < MAX_SUBTITLES; x++) { this->fd_subfilter[x] = open(this->demux_device, O_RDWR); if (this->fd_subfilter[x] < 0) { xprintf(this->xine, XINE_VERBOSITY_DEBUG, "DEMUX DEVICE Subtitle filter: %s\n", strerror(errno)); } } /* open EIT with NONBLOCK */ if(fcntl(this->fd_pidfilter[EITFILTER], F_SETFL, O_NONBLOCK)<0) xprintf(this->xine,XINE_VERBOSITY_DEBUG,"input_dvb: couldn't set EIT to nonblock: %s\n",strerror(errno)); /* and the internal filter used for PAT & PMT */ if(fcntl(this->fd_pidfilter[INTERNAL_FILTER], F_SETFL, O_NONBLOCK)<0) xprintf(this->xine,XINE_VERBOSITY_DEBUG,"input_dvb: couldn't set EIT to nonblock: %s\n",strerror(errno)); /* and the frontend */ fcntl(this->fd_frontend, F_SETFL, O_NONBLOCK); xprintf(this->xine,XINE_VERBOSITY_DEBUG,"input_dvb: Frontend is <%s> ",this->feinfo.name); if(this->feinfo.type==FE_QPSK) xprintf(this->xine,XINE_VERBOSITY_DEBUG,"SAT Card\n"); if(this->feinfo.type==FE_QAM) xprintf(this->xine,XINE_VERBOSITY_DEBUG,"CAB Card\n"); if(this->feinfo.type==FE_OFDM) xprintf(this->xine,XINE_VERBOSITY_DEBUG,"TER Card\n"); if ((test_video=open(video_device, O_RDWR)) < 0) { xprintf(this->xine,XINE_VERBOSITY_DEBUG,"input_dvb: Card has no hardware decoder\n"); }else{ xprintf(this->xine,XINE_VERBOSITY_DEBUG,"input_dvb: Card HAS HARDWARE DECODER\n"); close(test_video); } free(video_device); return this; } static int dvb_set_pidfilter(dvb_input_plugin_t * this, int filter, ushort pid, int pidtype, int taptype) { tuner_t *tuner = this->tuner; if(this->channels[this->channel].pid [filter] !=NOPID) { ioctl(tuner->fd_pidfilter[filter], DMX_STOP); } this->channels[this->channel].pid [filter] = pid; tuner->pesFilterParams[filter].pid = pid; tuner->pesFilterParams[filter].input = DMX_IN_FRONTEND; tuner->pesFilterParams[filter].output = taptype; tuner->pesFilterParams[filter].pes_type = pidtype; tuner->pesFilterParams[filter].flags = DMX_IMMEDIATE_START; if (ioctl(tuner->fd_pidfilter[filter], DMX_SET_PES_FILTER, &tuner->pesFilterParams[filter]) < 0) { xprintf(tuner->xine, XINE_VERBOSITY_DEBUG, "input_dvb: set_pid: %s\n", strerror(errno)); return 0; } return 1; } static int dvb_set_sectfilter(dvb_input_plugin_t * this, int filter, ushort pid, int pidtype, char table, char mask) { tuner_t *tuner = this->tuner; if(this->channels[this->channel].pid [filter] !=NOPID) { ioctl(tuner->fd_pidfilter[filter], DMX_STOP); } this->channels[this->channel].pid [filter] = pid; tuner->sectFilterParams[filter].pid = pid; memset(&tuner->sectFilterParams[filter].filter.filter,0,DMX_FILTER_SIZE); memset(&tuner->sectFilterParams[filter].filter.mask,0,DMX_FILTER_SIZE); tuner->sectFilterParams[filter].timeout = 0; tuner->sectFilterParams[filter].filter.filter[0] = table; tuner->sectFilterParams[filter].filter.mask[0] = mask; tuner->sectFilterParams[filter].flags = DMX_IMMEDIATE_START; if (ioctl(tuner->fd_pidfilter[filter], DMX_SET_FILTER, &tuner->sectFilterParams[filter]) < 0){ xprintf(tuner->xine, XINE_VERBOSITY_DEBUG, "input_dvb: set_sectionfilter: %s\n", strerror(errno)); return 0; } return 1; } static int find_param(const Param *list, const char *name) { while (list->name && strcmp(list->name, name)) list++; return list->value;; } static int extract_channel_from_string(channel_t * channel,char * str,fe_type_t fe_type) { /* try to extract channel data from a string in the following format (DVBS) QPSK: :::::: (DVBC) QAM: ::::::: (DVBT) OFDM: ::: :::: :::: = any string not containing ':' = unsigned long = 'v' or 'h' = unsigned long, usually 0 :D = symbol rate in MSyms/sec = INVERSION_ON | INVERSION_OFF | INVERSION_AUTO = FEC_1_2, FEC_2_3, FEC_3_4 .... FEC_AUTO ... FEC_NONE = QPSK, QAM_128, QAM_16 ... = BANDWIDTH_6_MHZ, BANDWIDTH_7_MHZ, BANDWIDTH_8_MHZ = = = TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K = video program id = audio program id */ unsigned long freq; char *field, *tmp; tmp = str; /* find the channel name */ if(!(field = strsep(&tmp,":")))return -1; channel->name = strdup(field); /* find the frequency */ if(!(field = strsep(&tmp, ":")))return -1; freq = strtoul(field,NULL,0); switch(fe_type) { case FE_QPSK: if(freq > 11700) { channel->front_param.frequency = (freq - 10600)*1000; channel->tone = 1; } else { channel->front_param.frequency = (freq - 9750)*1000; channel->tone = 0; } channel->front_param.inversion = INVERSION_AUTO; /* find out the polarisation */ if(!(field = strsep(&tmp, ":")))return -1; channel->pol = (field[0] == 'h' ? 0 : 1); /* satellite number */ if(!(field = strsep(&tmp, ":")))return -1; channel->sat_no = strtoul(field, NULL, 0); /* symbol rate */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.qpsk.symbol_rate = strtoul(field, NULL, 0) * 1000; channel->front_param.u.qpsk.fec_inner = FEC_AUTO; break; case FE_QAM: channel->front_param.frequency = freq; /* find out the inversion */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.inversion = find_param(inversion_list, field); /* find out the symbol rate */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.qam.symbol_rate = strtoul(field, NULL, 0); /* find out the fec */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.qam.fec_inner = find_param(fec_list, field); /* find out the qam */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.qam.modulation = find_param(qam_list, field); break; case FE_OFDM: channel->front_param.frequency = freq; /* find out the inversion */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.inversion = find_param(inversion_list, field); /* find out the bandwidth */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.ofdm.bandwidth = find_param(bw_list, field); /* find out the fec_hp */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.ofdm.code_rate_HP = find_param(fec_list, field); /* find out the fec_lp */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.ofdm.code_rate_LP = find_param(fec_list, field); /* find out the qam */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.ofdm.constellation = find_param(qam_list, field); /* find out the transmission mode */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.ofdm.transmission_mode = find_param(transmissionmode_list, field); /* guard list */ if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.ofdm.guard_interval = find_param(guard_list, field); if(!(field = strsep(&tmp, ":")))return -1; channel->front_param.u.ofdm.hierarchy_information = find_param(hierarchy_list, field); break; } /* Video PID - not used but we'll take it anyway */ if (!(field = strsep(&tmp, ":"))) return -1; channel->pid[VIDFILTER] = strtoul(field, NULL, 0); /* Audio PID - it's only for mpegaudio so we don't use it anymore */ if (!(field = strsep(&tmp, ":"))) return -1; channel->pid[AUDFILTER] = strtoul(field, NULL, 0); /* service ID */ if (!(field = strsep(&tmp, ":"))) return -1; channel->service_id = strtoul(field, NULL, 0); /* some channel.conf files are generated with the service ID 1 to the right this needs investigation */ if ((field = strsep(&tmp, ":"))) if(strtoul(field,NULL,0)>0) channel->service_id = strtoul(field, NULL, 0); return 0; } static channel_t *load_channels(dvb_input_plugin_t *this, int *num_ch, fe_type_t fe_type) { FILE *f; char str[BUFSIZE]; char filename[BUFSIZE]; channel_t *channels; int num_channels; xine_t *xine = this->class->xine; snprintf(filename, BUFSIZE, "%s/.xine/channels.conf", xine_get_homedir()); f = fopen(filename, "rb"); if (!f) { xprintf(xine, XINE_VERBOSITY_LOG, _("input_dvb: failed to open dvb channel file '%s'\n"), filename); _x_message(this->stream, XINE_MSG_FILE_NOT_FOUND, filename, "Please run the dvbscan utility."); return NULL; } /* * count and alloc channels */ num_channels = 0; while ( fgets (str, BUFSIZE, f)) { num_channels++; } fclose (f); if(num_channels > 0) xprintf (xine, XINE_VERBOSITY_DEBUG, "input_dvb: expecting %d channels...\n", num_channels); else { xprintf (xine, XINE_VERBOSITY_DEBUG, "input_dvb: no channels found in the file: giving up.\n"); return NULL; } channels = xine_xmalloc (sizeof (channel_t) * num_channels); /* * load channel list */ f = fopen (filename, "rb"); num_channels = 0; while ( fgets (str, BUFSIZE, f)) { if(extract_channel_from_string(&(channels[num_channels]),str,fe_type) < 0)continue; num_channels++; } if(num_channels > 0) xprintf (xine, XINE_VERBOSITY_DEBUG, "input_dvb: found %d channels...\n", num_channels); else { xprintf (xine, XINE_VERBOSITY_DEBUG, "input_dvb: no channels found in the file: giving up.\n"); free(channels); return NULL; } *num_ch = num_channels; return channels; } static int tuner_set_diseqc(tuner_t *this, channel_t *c) { struct dvb_diseqc_master_cmd cmd = {{0xe0, 0x10, 0x38, 0xf0, 0x00, 0x00}, 4}; cmd.msg[3] = 0xf0 | ((c->sat_no * 4) & 0x0f) | (c->tone ? 1 : 0) | (c->pol ? 0 : 2); if (ioctl(this->fd_frontend, FE_SET_TONE, SEC_TONE_OFF) < 0) return 0; if (ioctl(this->fd_frontend, FE_SET_VOLTAGE, c->pol ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18) < 0) return 0; usleep(15000); if (ioctl(this->fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &cmd) < 0) return 0; usleep(15000); if (ioctl(this->fd_frontend, FE_DISEQC_SEND_BURST, (c->sat_no / 4) % 2 ? SEC_MINI_B : SEC_MINI_A) < 0) return 0; usleep(15000); if (ioctl(this->fd_frontend, FE_SET_TONE, c->tone ? SEC_TONE_ON : SEC_TONE_OFF) < 0) return 0; return 1; } /* Tune to the requested freq. etc, wait for frontend to lock for a few seconds. * if frontend can't lock, retire. */ static int tuner_tune_it (tuner_t *this, struct dvb_frontend_parameters *front_param) { fe_status_t status; fe_status_t festatus; struct dvb_frontend_event event; unsigned int strength; /* discard stale events */ while (1) { if (ioctl(this->fd_frontend, FE_GET_EVENT, &event) == -1) break; } if (ioctl(this->fd_frontend, FE_SET_FRONTEND, front_param) <0) { xprintf(this->xine, XINE_VERBOSITY_DEBUG, "setfront front: %s\n", strerror(errno)); } do { if (ioctl(this->fd_frontend, FE_READ_STATUS, &status) < 0) { xprintf(this->xine, XINE_VERBOSITY_DEBUG, "fe get event: %s\n", strerror(errno)); return 0; } xprintf(this->xine, XINE_VERBOSITY_DEBUG, "input_dvb: status: %x\n", status); if (status & FE_HAS_LOCK) { ioctl(this->fd_frontend, FE_READ_STATUS, &event.status); break; } usleep(500000); } while (!(status & FE_TIMEDOUT)); /* inform the user of frontend status */ festatus=0; xprintf(this->xine,XINE_VERBOSITY_LOG,"input_dvb: Tuner status: "); if(ioctl(this->fd_frontend,FE_READ_STATUS,&festatus) >= 0){ if (festatus & FE_HAS_SIGNAL) xprintf(this->xine,XINE_VERBOSITY_LOG," FE_HAS_SIGNAL"); if (festatus & FE_TIMEDOUT) xprintf(this->xine,XINE_VERBOSITY_LOG," FE_TIMEDOUT"); if (festatus & FE_HAS_LOCK) xprintf(this->xine,XINE_VERBOSITY_LOG," FE_HAS_LOCK"); if (festatus & FE_HAS_CARRIER) xprintf(this->xine,XINE_VERBOSITY_LOG," FE_HAS_CARRIER"); if (festatus & FE_HAS_VITERBI) xprintf(this->xine,XINE_VERBOSITY_LOG," FE_HAS_VITERBI"); if (festatus & FE_HAS_SYNC) xprintf(this->xine,XINE_VERBOSITY_LOG," FE_HAS_SYNC"); } xprintf(this->xine,XINE_VERBOSITY_LOG,"\n"); strength=0; if(ioctl(this->fd_frontend,FE_READ_BER,&strength) >= 0) xprintf(this->xine,XINE_VERBOSITY_LOG,"input_dvb: Bit error rate: %i\n",strength); strength=0; if(ioctl(this->fd_frontend,FE_READ_SIGNAL_STRENGTH,&strength) >= 0) xprintf(this->xine,XINE_VERBOSITY_LOG,"input_dvb: Signal strength: %i\n",strength); strength=0; if(ioctl(this->fd_frontend,FE_READ_SNR,&strength) >= 0) xprintf(this->xine,XINE_VERBOSITY_LOG,"input_dvb: Signal/Noise Ratio: %i\n",strength); if (event.status & FE_HAS_LOCK) { xprintf(this->xine,XINE_VERBOSITY_LOG,"input_dvb: Lock achieved at %.lu Hz\n",(unsigned long)front_param->frequency); return 1; } else { xprintf(this->xine,XINE_VERBOSITY_LOG,"input_dvb: Unable to achieve lock at %.lu Hz\n",(unsigned long)front_param->frequency); return 0; } } /* Parse the PMT, and add filters for all stream types associated with * the 'channel'. We leave it to the demuxer to sort out which PIDs to * use. to simplify things slightly, (and because the demuxer can't handle it) * allow only one of each media type */ static void parse_pmt(dvb_input_plugin_t *this, const unsigned char *buf, int section_length) { int program_info_len; int pcr_pid; int has_video=0; int has_audio=0; int has_ac3=0; int has_subs=0; int has_text=0; dvb_set_pidfilter(this, PMTFILTER, this->channels[this->channel].pmtpid, DMX_PES_OTHER,DMX_OUT_TS_TAP); dvb_set_pidfilter(this, PATFILTER, 0, DMX_PES_OTHER,DMX_OUT_TS_TAP); pcr_pid = ((buf[0] & 0x1f) << 8) | buf[1]; if(pcr_pid!=0x1FFF) /* don't waste time if the PCR is invalid */ dvb_set_pidfilter(this, PCRFILTER, pcr_pid, DMX_PES_PCR,DMX_OUT_TS_TAP); program_info_len = ((buf[2] & 0x0f) << 8) | buf[3]; buf += program_info_len + 4; section_length -= program_info_len + 4; while (section_length >= 5) { int elementary_pid = ((buf[1] & 0x1f) << 8) | buf[2]; int descriptor_len = ((buf[3] & 0x0f) << 8) | buf[4]; switch (buf[0]) { case 0x01: case 0x02: if(!has_video) { xprintf(this->stream->xine,XINE_VERBOSITY_LOG,"input_dvb: Adding VIDEO : PID 0x%04x\n", elementary_pid); dvb_set_pidfilter(this, VIDFILTER, elementary_pid, DMX_PES_VIDEO, DMX_OUT_TS_TAP); has_video=1; } break; case 0x03: case 0x04: if(!has_audio) { xprintf(this->stream->xine,XINE_VERBOSITY_LOG,"input_dvb: Adding AUDIO : PID 0x%04x\n", elementary_pid); dvb_set_pidfilter(this, AUDFILTER, elementary_pid, DMX_PES_AUDIO, DMX_OUT_TS_TAP); has_audio=1; } break; case 0x06: if (find_descriptor(0x56, buf + 5, descriptor_len, NULL, NULL)) { if(!has_text) { xprintf(this->stream->xine,XINE_VERBOSITY_LOG,"input_dvb: Adding TELETEXT : PID 0x%04x\n", elementary_pid); dvb_set_pidfilter(this,TXTFILTER, elementary_pid, DMX_PES_OTHER,DMX_OUT_TS_TAP); has_text=1; } break; } else if (find_descriptor (0x59, buf + 5, descriptor_len, NULL, NULL)) { /* Note: The subtitling descriptor can also signal * teletext subtitling, but then the teletext descriptor * will also be present; so we can be quite confident * that we catch DVB subtitling streams only here, w/o * parsing the descriptor. */ if(has_subs <= MAX_SUBTITLES) { xprintf(this->stream->xine,XINE_VERBOSITY_LOG,"input_dvb: Adding SUBTITLES: PID 0x%04x\n", elementary_pid); if(this->channels[this->channel].subpid [has_subs] !=NOPID) { ioctl(this->tuner->fd_subfilter[has_subs], DMX_STOP); } this->channels[this->channel].subpid [has_subs] = elementary_pid; this->tuner->subFilterParams[has_subs].pid = elementary_pid; this->tuner->subFilterParams[has_subs].input = DMX_IN_FRONTEND; this->tuner->subFilterParams[has_subs].output = DMX_OUT_TS_TAP; this->tuner->subFilterParams[has_subs].pes_type = DMX_PES_OTHER; this->tuner->subFilterParams[has_subs].flags = DMX_IMMEDIATE_START; if (ioctl(this->tuner->fd_subfilter[has_subs], DMX_SET_PES_FILTER, &this->tuner->subFilterParams[has_subs]) < 0) { xprintf(this->tuner->xine, XINE_VERBOSITY_DEBUG, "input_dvb: set_pid: %s\n", strerror(errno)); break; } has_subs++; } break; } else if (find_descriptor (0x6a, buf + 5, descriptor_len, NULL, NULL)) { if(!has_ac3) { dvb_set_pidfilter(this, AC3FILTER, elementary_pid, DMX_PES_OTHER,DMX_OUT_TS_TAP); xprintf(this->stream->xine,XINE_VERBOSITY_LOG,"input_dvb: Adding AC3 : PID 0x%04x\n", elementary_pid); has_ac3=1; } break; } break; }; buf += descriptor_len + 5; section_length -= descriptor_len + 5; }; } static void dvb_parse_si(dvb_input_plugin_t *this) { char *tmpbuffer; char *bufptr; int service_id; int result; int section_len; int x; struct pollfd pfd; tuner_t *tuner = this->tuner; tmpbuffer = xine_xmalloc (8192); bufptr = tmpbuffer; pfd.fd=tuner->fd_pidfilter[INTERNAL_FILTER]; pfd.events = POLLPRI; xprintf(this->stream->xine,XINE_VERBOSITY_DEBUG,"input_dvb: Setting up Internal PAT filter\n"); xine_usec_sleep(500000); /* first - the PAT. retrieve the entire section...*/ dvb_set_sectfilter(this, INTERNAL_FILTER, 0, DMX_PES_OTHER, 0, 0xff); /* wait for up to 15 seconds */ if(poll(&pfd,1,12000)<1) /* PAT timed out - weird, but we'll default to using channels.conf info */ { xprintf(this->stream->xine,XINE_VERBOSITY_LOG,"input_dvb: Error setting up Internal PAT filter - reverting to rc6 hehaviour\n"); dvb_set_pidfilter (this,VIDFILTER,this->channels[this->channel].pid[VIDFILTER], DMX_PES_OTHER, DMX_OUT_TS_TAP); dvb_set_pidfilter (this,AUDFILTER,this->channels[this->channel].pid[AUDFILTER], DMX_PES_OTHER, DMX_OUT_TS_TAP); return; } result = read (tuner->fd_pidfilter[INTERNAL_FILTER], tmpbuffer, 3); if(result!=3) xprintf(this->stream->xine,XINE_VERBOSITY_DEBUG,"input_dvb: error reading PAT table - no data!\n"); section_len = getbits(tmpbuffer,12,12); result = read (tuner->fd_pidfilter[INTERNAL_FILTER], tmpbuffer+5,section_len); if(result!=section_len) xprintf(this->stream->xine,XINE_VERBOSITY_DEBUG,"input_dvb: error reading in the PAT table\n"); ioctl(tuner->fd_pidfilter[INTERNAL_FILTER], DMX_STOP); bufptr+=10; this->num_streams_in_this_ts=0; section_len-=5; while(section_len>4){ service_id = getbits (bufptr,0,16); for (x=0;xnum_channels;x++){ if(this->channels[x].service_id==service_id) { this->channels[x].pmtpid = getbits (bufptr, 19, 13); } } section_len-=4; bufptr+=4; if(service_id>0) /* ignore NIT table for now */ this->num_streams_in_this_ts++; } bufptr = tmpbuffer; /* next - the PMT */ xprintf(this->stream->xine,XINE_VERBOSITY_DEBUG,"input_dvb: Setting up Internal PMT filter for pid %x\n",this->channels[this->channel].pmtpid); dvb_set_sectfilter(this, INTERNAL_FILTER, this->channels[this->channel].pmtpid, DMX_PES_OTHER, 2, 0xff); if((poll(&pfd,1,15000)<1) || this->channels[this->channel].pmtpid==0) /* PMT timed out or couldn't be found - default to using channels.conf info */ { xprintf(this->stream->xine,XINE_VERBOSITY_LOG,"input_dvb: WARNING **** Reverting to rc6 hehaviour. Please regenerate your channels.conf in ?zap format ****\n"); dvb_set_pidfilter (this,VIDFILTER,this->channels[this->channel].pid[VIDFILTER], DMX_PES_OTHER, DMX_OUT_TS_TAP); dvb_set_pidfilter (this,AUDFILTER,this->channels[this->channel].pid[AUDFILTER], DMX_PES_OTHER, DMX_OUT_TS_TAP); return; } result = read(tuner->fd_pidfilter[INTERNAL_FILTER],tmpbuffer,3); section_len = getbits (bufptr, 12, 12); result = read(tuner->fd_pidfilter[INTERNAL_FILTER],tmpbuffer+3,section_len); ioctl(tuner->fd_pidfilter[INTERNAL_FILTER], DMX_STOP); parse_pmt(this,tmpbuffer+8,section_len); /* dvb_set_pidfilter(this, TSDTFILTER, 0x02,DMX_PES_OTHER,DMX_OUT_TS_TAP); dvb_set_pidfilter(this, RSTFILTER, 0x13,DMX_PES_OTHER,DMX_OUT_TS_TAP); dvb_set_pidfilter(this, TDTFILTER, 0x14,DMX_PES_OTHER,DMX_OUT_TS_TAP); dvb_set_pidfilter(this, DITFILTER, 0x1e,DMX_PES_OTHER,DMX_OUT_TS_TAP); dvb_set_pidfilter(this, CATFILTER, 0x01,DMX_PES_OTHER,DMX_OUT_TS_TAP); dvb_set_pidfilter(this, NITFILTER, 0x10,DMX_PES_OTHER,DMX_OUT_TS_TAP); dvb_set_pidfilter(this, SDTFILTER, 0x11, DMX_PES_OTHER, DMX_OUT_TS_TAP); */ /* we use the section filter for EIT because we are guarenteed a complete section */ if(ioctl(tuner->fd_pidfilter[EITFILTER],DMX_SET_BUFFER_SIZE,8192*this->num_streams_in_this_ts)<0) xprintf(this->stream->xine,XINE_VERBOSITY_DEBUG,"input_dvb: couldn't increase buffer size for EIT: %s \n",strerror(errno)); dvb_set_sectfilter(this, EITFILTER, 0x12,DMX_PES_OTHER,0x4e, 0xff); xprintf(this->stream->xine,XINE_VERBOSITY_DEBUG,"input_dvb: Setup of PID filters complete\n"); free(tmpbuffer); } /* this function parses the EIT table. It needs to be called at least twice - once for current program, once for next */ static void do_eit(dvb_input_plugin_t *this) { int table_id; int descriptor_id; int section_len=0; unsigned int service_id=-1; int n,y,x; char *eit=NULL; char *foo=NULL; int text_len; int current_next=0; int running_status=0; struct pollfd fd; char *buffer; int loops; int current_channel; foo = xine_xmalloc(8192); fd.fd = this->tuner->fd_pidfilter[EITFILTER]; fd.events = POLLPRI; /* reset all pointers to NULL - if there's info available, we'll find it */ for(x=0;xnum_channels;x++){ this->channels[x].eit[0].progname=NULL; this->channels[x].eit[0].description=NULL; this->channels[x].eit[0].starttime=NULL; this->channels[x].eit[0].duration=NULL; this->channels[x].eit[0].content=NULL; this->channels[x].eit[1].progname=NULL; this->channels[x].eit[1].description=NULL; this->channels[x].eit[1].starttime=NULL; this->channels[x].eit[1].duration=NULL; this->channels[x].eit[1].content=NULL; } /* we assume that because we are displaying the channel, service_id for this channel must be known. We should be accepting all information into the various channel structs, with a time value so we only really check once every five minutes or so per TS. Each section delivered by the driver contains info about 1 service. so we may need to read it multiple times to retrieve the one we're after. */ for(loops=0;loops<=this->num_streams_in_this_ts*2;loops++){ eit=foo; if (poll(&fd,1,2000)<1) { xprintf(this->stream->xine,XINE_VERBOSITY_LOG,"(TImeout in EPG loop!! Quitting\n"); return; } current_channel=-1; n = read (this->tuner->fd_pidfilter[EITFILTER],eit,3); table_id=getbits(eit,0,8); section_len=(unsigned int)getbits(eit,12,12); n = read (this->tuner->fd_pidfilter[EITFILTER],eit+3,section_len); service_id=(unsigned int)getbits(eit, 24, 16); for (n=0;nnum_channels;n++) if(this->channels[n].service_id==service_id) current_channel=n; current_next=getbits(foo,47,1); if(section_len>15){ /* do we have information about the current program, or the next? */ if(getbits(foo,192,3) > 2){ running_status=0; /* not currently running - must be next */ } else { running_status=1; /* currently running */ } /* allocate an area in the eit buffer & clear it */ buffer=this->eitbuffer+(current_channel*2048); y=running_status*1024; this->channels[current_channel].eit[running_status].progname=buffer+y; this->channels[current_channel].eit[running_status].description=buffer+y+256; this->channels[current_channel].eit[running_status].starttime=buffer+y+768; this->channels[current_channel].eit[running_status].duration=buffer+y+812; this->channels[current_channel].eit[running_status].content=buffer+y+900; memset(buffer+y,0,1024); /* gather up the start time and duration for this program unless duration is 0. no point in cluttering up the OSD... */ if(bcdtoint(eit[21])+bcdtoint(eit[22])+bcdtoint(eit[23])>0){ strftime(this->channels[current_channel].eit[running_status].starttime,21,"%a %l:%M%p",dvb_mjdtime(eit+16)); snprintf(this->channels[current_channel].eit[running_status].duration,21,"%i:%02i",(char)bcdtoint(eit[21] & 0xff),(char)bcdtoint(eit[22] & 0xff)); } descriptor_id=eit[26]; eit+=27; section_len-=27; /* run the descriptor loop for the length of section_len */ while (section_len>1) { switch(descriptor_id) { case 0x4A: /* linkage descriptor */ break; case 0x4D: { /* simple program info descriptor */ int name_len; int desc_len; xine_cfg_entry_t language; desc_len = getbits(eit, 0, 8); /* Let's get the EPG data only in the wanted language. */ xine_config_lookup_entry(this->stream->xine, "media.dvd.language", &language); if (language.str_value && *language.str_value && strncmp(language.str_value, &eit[1], 2)) { /*printf("### Skipping language: %C%C%C\n",eit[1],eit[2],eit[3]); */ break; } /* program name */ name_len = (unsigned char)eit[4]; memcpy(this->channels[current_channel].eit[running_status].progname, eit + 5, name_len); /* detailed program information (max 256 chars)*/ text_len = (unsigned char)eit[5+name_len]; memcpy(this->channels[current_channel].eit[running_status].description, eit + 6 + name_len, text_len); } break; case 0x4E: { /* extended descriptor - not currently used as the simple descriptor gives us enough for now and the only broadcaster in my locale using it sends an empty one... */ /* int item_desc_len; int total_item_len; int y; int desc_len; desc_len=getbits(eit,0,8); printf("Descriptornum: %i\n",getbits(eit,8,4)); printf("LastDescNum: %i\n",getbits(eit,12,4)); printf("Language: %C%C%C\n",(char)getbits(eit,16,8),(char)getbits(eit,24,8),(char)getbits(eit,32,8)); total_item_len = getbits(eit,40,8); item_desc_len = getbits(eit,48,8); if(item_desc_len==1) printf("-"); else for(y=0;ychannels[current_channel].eit[running_status].content, 40, content[content_bits]); } break; case 0x55: { /* Parental Rating descriptor describes minimum recommened age -3 */ /* printf("descriptor Len: %i\n",getbits(eit,0,8)); printf("Country Code: %C%C%C\n",eit[1],eit[2],eit[3]); */ /* A rating value of 0 means that there is no rating defined. Ratings greater than 0xF are "defined by broadcaster", which is not supported for now. */ if (eit[4] > 0 && eit[4] <= 0xF) this->channels[current_channel].eit[running_status].rating = eit[4] + 3; else this->channels[current_channel].eit[running_status].rating = 0; } break; case 0x57: /* telephone descriptor not used */ break; case 0x5E: /* multilingual component descriptor - we should be using this... */ break; case 0x5F: /* private data specifier */ case 0x61: /* short smoothing buffer should use this to reduce buffers when avail */ case 0x64: /* data broadcast descriptor */ case 0x69: /* PDC descriptor?? */ case 0x75: /* TVA_ID descriptor */ case 0x76: /* content identifier */ default: break; } section_len-=getbits(eit,0,8)+2; eit+=getbits(eit,0,8); descriptor_id=eit[1]; eit+=2; } } } free(foo); } static void show_eit(dvb_input_plugin_t *this) { char *line; char *description; int x,y,ok; if(this->displaying==0){ this->displaying=1; line=xine_xmalloc(512); this->stream->osd_renderer->clear(this->proginfo_osd); /* Channel Name */ this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 32); this->stream->osd_renderer->render_text (this->proginfo_osd,350,150,"Searching for Info",OSD_TEXT4); this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 24); this->stream->osd_renderer->show (this->background, 0); this->stream->osd_renderer->show_unscaled (this->proginfo_osd, 0); do_eit(this); this->stream->osd_renderer->hide (this->proginfo_osd, 0); this->stream->osd_renderer->clear(this->proginfo_osd); /* Channel Name */ this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 32); this->stream->osd_renderer->render_text (this->proginfo_osd,15,10,this->channels[this->channel].name,OSD_TEXT4); this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 24); this->stream->osd_renderer->render_text (this->proginfo_osd, 15, 50, "NOW:",OSD_TEXT3); this->stream->osd_renderer->render_text (this->proginfo_osd, 15, 300, "NEXT:",OSD_TEXT3); if(this->channels[this->channel].eit[0].progname!=NULL) { this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 32); /* we trim the program name down to 23 chars so it'll fit although with kerning you just don't know */ snprintf(line, MAX_PROGRAM_NAME_LENGTH, "%s", this->channels[this->channel].eit[0].progname); this->stream->osd_renderer->render_text (this->proginfo_osd, 100, 46, line,OSD_TEXT4); this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 24); /*start time and duration */ y=0; if(strlen(this->channels[this->channel].eit[0].starttime)>3){ snprintf(line,100,"%s (%s)",this->channels[this->channel].eit[0].starttime, this->channels[this->channel].eit[0].duration); if(strlen(this->channels[this->channel].eit[0].content)>3) y=15; /* offset vertically */ this->stream->osd_renderer->render_text (this->proginfo_osd, 670, 50+y, line,OSD_TEXT3); } /*Content Type and Rating if any*/ if(strlen(this->channels[this->channel].eit[0].content)>3){ int prog_rating; snprintf(line, 94, "%s", this->channels[this->channel].eit[0].content); prog_rating = this->channels[this->channel].eit[0].rating; if (prog_rating > 0) { snprintf(line + strlen(line), 7, " (%i+)", prog_rating); } this->stream->osd_renderer->render_text (this->proginfo_osd, 670, 20+y, line,OSD_TEXT3); } /* some quick'n'dirty formatting to keep words whole */ ok=0; y=0; description=this->channels[this->channel].eit[0].description; while(ok<250){ x=65; while(getbits(description,x*8,8)>' ' && getbits(description,x*8,8)<254){ x--; } x++; ok+=x; snprintf(line,x,"%s",description); this->stream->osd_renderer->render_text (this->proginfo_osd, 55, 110+(y),line,OSD_TEXT3); description+=x; y+=30; } } else { this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 32); /* we trim the program name down so it'll fit although with kerning you just don't know */ snprintf(line, MAX_PROGRAM_NAME_LENGTH, "%s", "No Information Available"); this->stream->osd_renderer->render_text (this->proginfo_osd, 100, 46, line,OSD_TEXT4); this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 24); } if(this->channels[this->channel].eit[1].progname!=NULL) { /* and now the next program */ snprintf(line, MAX_PROGRAM_NAME_LENGTH, "%s", this->channels[this->channel].eit[1].progname); this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 32); this->stream->osd_renderer->render_text (this->proginfo_osd, 100, 296, line,OSD_TEXT4); this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 24); y=0; if(strlen(this->channels[this->channel].eit[1].starttime)>3){ snprintf(line,100,"%s (%s)",this->channels[this->channel].eit[1].starttime, this->channels[this->channel].eit[1].duration); if(strlen(this->channels[this->channel].eit[1].content)>3) y=15; /* offset vertically */ this->stream->osd_renderer->render_text (this->proginfo_osd, 670, 300+y, line,OSD_TEXT3); } /*Content Type and Rating if any*/ if(strlen(this->channels[this->channel].eit[1].content)>3){ int prog_rating; snprintf(line, 94, "%s", this->channels[this->channel].eit[1].content); prog_rating = this->channels[this->channel].eit[1].rating; if (prog_rating > 0) { snprintf(line + strlen(line), 7, " (%i+)", prog_rating); } this->stream->osd_renderer->render_text (this->proginfo_osd, 670, 270+y, line,OSD_TEXT3); } /* some quick'n'dirty formatting to keep words whole */ ok=0; y=0; description=this->channels[this->channel].eit[1].description; while( ok < 250 ) { x=65; while(getbits(description,x*8,8)>' ' && getbits(description,x*8,8)<254){ x--; } x++; ok+=x; snprintf(line,x,"%s",description); this->stream->osd_renderer->render_text (this->proginfo_osd, 55, 360+(y),line,OSD_TEXT3); description+=x; y+=30; } } else { this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 32); /* we trim the program name down to 23 chars so it'll fit although with kerning you just don't know */ snprintf(line,28,"%s","No Information Available"); this->stream->osd_renderer->render_text (this->proginfo_osd, 100, 296, line,OSD_TEXT4); this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 24); } this->stream->osd_renderer->show(this->background,0); this->stream->osd_renderer->show_unscaled (this->proginfo_osd, 0); /* vpts=this->stream->xine->clock->get_current_time(this->stream->xine->clock); vpts+=1080000; this->stream->osd_renderer->hide (this->proginfo_osd,vpts); this->stream->osd_renderer->hide (this->background,vpts); */ free(line); } else { this->displaying=0; this->stream->osd_renderer->hide (this->proginfo_osd,0); this->stream->osd_renderer->hide (this->background,0); } return; } static int tuner_set_channel (dvb_input_plugin_t *this, channel_t *c) { tuner_t *tuner=this->tuner; if (tuner->feinfo.type==FE_QPSK) { if(!(tuner->feinfo.caps & FE_CAN_INVERSION_AUTO)) c->front_param.inversion = INVERSION_OFF; if (!tuner_set_diseqc(tuner, c)) return 0; } if (!tuner_tune_it (tuner, &c->front_param)) return 0; return 1; /* fixme: error handling */ } static void osd_show_channel (dvb_input_plugin_t *this) { int i, channel ; this->stream->osd_renderer->filled_rect (this->channel_osd, 0, 0, 395, 400, 2); channel = this->channel - 5; for (i=0; i<11; i++) { if ( (channel >= 0) && (channel < this->num_channels) ) this->stream->osd_renderer->render_text (this->channel_osd, 110, 10+i*35, this->channels[channel].name, OSD_TEXT3); channel ++; } this->stream->osd_renderer->line (this->channel_osd, 105, 183, 390, 183, 10); this->stream->osd_renderer->line (this->channel_osd, 105, 183, 105, 219, 10); this->stream->osd_renderer->line (this->channel_osd, 105, 219, 390, 219, 10); this->stream->osd_renderer->line (this->channel_osd, 390, 183, 390, 219, 10); this->stream->osd_renderer->show (this->channel_osd, 0); /* hide eit if showing */ if (this->displaying==1) { this->stream->osd_renderer->hide (this->proginfo_osd,0); this->stream->osd_renderer->hide (this->background,0); } } static void switch_channel (dvb_input_plugin_t *this) { int x; xine_event_t event; xine_pids_data_t data; xine_ui_data_t ui_data; /* control_nop appears to stop an occasional (quite long) pause between channel-changes, which the user may see as a lockup. */ _x_demux_control_nop(this->stream, BUF_FLAG_END_STREAM); _x_demux_flush_engine(this->stream); pthread_mutex_lock (&this->mutex); close (this->fd); for (x = 0; x < MAXFILTERS; x++) { close(this->tuner->fd_pidfilter[x]); this->tuner->fd_pidfilter[x] = open(this->tuner->demux_device, O_RDWR); } if (!tuner_set_channel (this, &this->channels[this->channel])) { xprintf (this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: tuner_set_channel failed\n")); pthread_mutex_unlock (&this->mutex); return; } event.type = XINE_EVENT_PIDS_CHANGE; data.vpid = this->channels[this->channel].pid[VIDFILTER]; data.apid = this->channels[this->channel].pid[AUDFILTER]; event.data = &data; event.data_length = sizeof (xine_pids_data_t); xprintf (this->class->xine, XINE_VERBOSITY_DEBUG, "input_dvb: sending event\n"); xine_event_send (this->stream, &event); snprintf (ui_data.str, 256, "%04d - %s", this->channel, this->channels[this->channel].name); ui_data.str_len = strlen (ui_data.str); _x_meta_info_set(this->stream, XINE_META_INFO_TITLE, ui_data.str); event.type = XINE_EVENT_UI_SET_TITLE; event.stream = this->stream; event.data = &ui_data; event.data_length = sizeof(ui_data); xine_event_send(this->stream, &event); xprintf(this->class->xine,XINE_VERBOSITY_DEBUG,"ui title event sent\n"); this->fd = open (this->tuner->dvr_device, O_RDONLY | O_NONBLOCK); pthread_mutex_unlock (&this->mutex); /* now read the pat,find all accociated PIDs and add them to the stream */ dvb_parse_si(this); this->stream->osd_renderer->hide(this->channel_osd,0); /* show eit for this channel if necessary */ if(this->displaying==1){ this->displaying=0; show_eit(this); } } static void do_record (dvb_input_plugin_t *this) { struct tm *tma; time_t *t; char filename [256]; char dates[64]; int x=0; xine_cfg_entry_t savedir; if (this->record_fd > -1) { /* stop recording */ close (this->record_fd); this->record_fd = -1; this->stream->osd_renderer->hide (this->rec_osd, 0); this->stream->osd_renderer->hide (this->paused_osd, 0); this->record_paused=0; } else { t=xine_xmalloc(sizeof(time_t)); time(t); tma=localtime(t); free(t); strftime(dates,63,"%F_%H%M",tma); if (xine_config_lookup_entry(this->stream->xine, "media.capture.save_dir", &savedir)){ if(strlen(savedir.str_value)>1){ if(opendir(savedir.str_value)==NULL){ snprintf (filename, 256, "%s/%s_%s.ts",xine_get_homedir(),this->channels[this->channel].name, dates); xprintf(this->class->xine,XINE_VERBOSITY_LOG,"savedir is wrong... saving to home directory\n"); } else { snprintf (filename, 256, "%s/%s_%s.ts",savedir.str_value,this->channels[this->channel].name, dates); xprintf(this->class->xine,XINE_VERBOSITY_LOG,"saving to savedir\n"); } } else { snprintf (filename, 256, "%s/%s_%s.ts",xine_get_homedir(),this->channels[this->channel].name, dates); xprintf(this->class->xine,XINE_VERBOSITY_LOG,"Saving to HomeDir\n"); } } /* remove spaces from name */ while((filename[x]!=0) && x<255){ if(filename[x]==' ') filename[x]='_'; x++; } /* start recording */ this->record_fd = open (filename, O_CREAT | O_APPEND | O_WRONLY, 0644); this->stream->osd_renderer->clear (this->rec_osd); this->stream->osd_renderer->render_text (this->rec_osd, 10, 10, "Recording to:", OSD_TEXT3); this->stream->osd_renderer->render_text (this->rec_osd, 160, 10, filename, OSD_TEXT3); this->stream->osd_renderer->show_unscaled (this->rec_osd, 0); } } static void dvb_event_handler (dvb_input_plugin_t *this) { xine_event_t *event; static int channel_menu_visible = 0; while ((event = xine_event_get (this->event_queue))) { xprintf(this->class->xine,XINE_VERBOSITY_DEBUG,"got event %08x\n", event->type); if (this->fd<0) { xine_event_free (event); return; } switch (event->type) { case XINE_EVENT_INPUT_MOUSE_BUTTON: { xine_input_data_t *input = (xine_input_data_t*)event->data; switch (input->button) { case MOUSE_BUTTON_LEFT: if (channel_menu_visible) { channel_menu_visible = 0; switch_channel (this); } #if 0 /* disable mouse left click -> EPG for now. */ else { show_eit(this); } #endif break; case MOUSE_WHEEL_UP: if (this->channel>0) this->channel--; channel_menu_visible = 1; osd_show_channel (this); break; case MOUSE_WHEEL_DOWN: if (this->channel < (this->num_channels-1)) this->channel++; channel_menu_visible = 1; osd_show_channel (this); break; case MOUSE_SIDE_LEFT: if (this->channel > 0) { this->channel--; channel_menu_visible = 0; switch_channel (this); } break; case MOUSE_SIDE_RIGHT: if (this->channel < (this->num_channels-1)) { this->channel++; channel_menu_visible = 0; switch_channel (this); } break; default: printf("Unknown mouse button number: %d.\n", input->button); /* Unknown mouse event. */ } break; } case XINE_EVENT_INPUT_DOWN: if (this->channel < (this->num_channels-1)) this->channel++; channel_menu_visible = 1; osd_show_channel (this); break; case XINE_EVENT_INPUT_UP: if (this->channel>0) this->channel--; channel_menu_visible = 1; osd_show_channel (this); break; case XINE_EVENT_INPUT_NEXT: if (this->channel < (this->num_channels-1)) { this->channel++; channel_menu_visible = 0; switch_channel (this); } break; case XINE_EVENT_INPUT_PREVIOUS: if (this->channel>0) { this->channel--; channel_menu_visible = 0; switch_channel (this); } break; case XINE_EVENT_INPUT_SELECT: channel_menu_visible = 0; switch_channel (this); break; case XINE_EVENT_INPUT_MENU1: this->stream->osd_renderer->hide (this->osd, 0); channel_menu_visible = 0; break; case XINE_EVENT_INPUT_MENU2: do_record (this); break; case XINE_EVENT_INPUT_MENU3: /* zoom for cropped 4:3 in a 16:9 window */ if (!this->zoom_ok) { this->zoom_ok = 1; this->stream->video_out->set_property (this->stream->video_out, VO_PROP_ZOOM_X, 133); this->stream->video_out->set_property (this->stream->video_out, VO_PROP_ZOOM_Y, 133); } else { this->zoom_ok=0; this->stream->video_out->set_property (this->stream->video_out, VO_PROP_ZOOM_X, 100); this->stream->video_out->set_property (this->stream->video_out, VO_PROP_ZOOM_Y, 100); } break; case XINE_EVENT_INPUT_MENU4: /* Pause recording.. */ if ((this->record_fd>-1) && (!this->record_paused)) { this->record_paused = 1; this->stream->osd_renderer->render_text (this->paused_osd, 15, 10, "Recording Paused",OSD_TEXT3); this->stream->osd_renderer->show_unscaled (this->paused_osd, 0); } else { this->record_paused=0; this->stream->osd_renderer->hide (this->paused_osd, 0); } break; case XINE_EVENT_INPUT_MENU7: channel_menu_visible = 0; show_eit(this); break; #if 0 default: printf ("input_dvb: got an event, type 0x%08x\n", event->type); #endif } xine_event_free (event); } } /* parse TS and re-write PAT to contain only our pmt */ static void ts_rewrite_packets (dvb_input_plugin_t *this, unsigned char * originalPkt, int len) { #define PKT_SIZE 188 #define BODY_SIZE (188-4) unsigned int sync_byte; unsigned int data_offset; unsigned int data_len; unsigned int pid; while(len>0){ sync_byte = originalPkt[0]; pid = ((originalPkt[1] << 8) | originalPkt[2]) & 0x1fff; /* * Discard packets that are obviously bad. */ data_offset = 4; originalPkt+=data_offset; if (pid == 0 && sync_byte==0x47) { unsigned long crc; originalPkt[3]=13; /* section length including CRC - first 3 bytes */ originalPkt[2]=0x80; originalPkt[7]=0; /* section number */ originalPkt[8]=0; /* last section number */ originalPkt[9]=(this->channels[this->channel].service_id >> 8) & 0xff; originalPkt[10]=this->channels[this->channel].service_id & 0xff; originalPkt[11]=(this->channels[this->channel].pmtpid >> 8) & 0xff; originalPkt[12]=this->channels[this->channel].pmtpid & 0xff; crc= ts_compute_crc32 (this, originalPkt+1, 12, 0xffffffff); originalPkt[13]=(crc>>24) & 0xff; originalPkt[14]=(crc>>16) & 0xff; originalPkt[15]=(crc>>8) & 0xff; originalPkt[16]=crc & 0xff; memset(originalPkt+17,0xFF,PKT_SIZE-21); /* stuff the remainder */ } data_len = PKT_SIZE - data_offset; originalPkt+=data_len; len-=data_len; } } static off_t dvb_plugin_read (input_plugin_t *this_gen, char *buf, off_t len) { dvb_input_plugin_t *this = (dvb_input_plugin_t *) this_gen; off_t n=0, total=0; int have_mutex=0; struct pollfd pfd; dvb_event_handler (this); xprintf(this->class->xine,XINE_VERBOSITY_DEBUG,"input_dvb: reading %lld bytes...\n", len); nbc_check_buffers (this->nbc); have_mutex=pthread_mutex_trylock( &this->mutex ); /* protect agains channel changes */ total=0; pfd.fd=this->fd; pfd.events = POLLPRI | POLLIN; while (totalclass->xine,XINE_VERBOSITY_LOG,"input_dvb: No data available. Signal Lost?? \n"); _x_demux_control_end(this->stream, BUF_FLAG_END_USER); this->read_failcount++; break; } if(this->read_failcount){ /* signal/stream regained after loss - kick the net_buf_control layer. */ this->read_failcount=0; xprintf(this->class->xine,XINE_VERBOSITY_LOG,"input_dvb: Data resumed...\n"); _x_demux_control_start(this->stream); } if(pfd.revents & POLLIN) n = read (this->fd, &buf[total], len-total); xprintf(this->class->xine,XINE_VERBOSITY_DEBUG,"input_dvb: got %lld bytes (%lld/%lld bytes read)\n", n,total,len); if (n > 0){ this->curpos += n; total += n; } else if (n<0 && errno!=EAGAIN) { break; } } ts_rewrite_packets (this, buf,total); if ((this->record_fd)&&(!this->record_paused)) write (this->record_fd, buf, total); if(have_mutex==0) pthread_mutex_unlock( &this->mutex ); /* no data for several seconds - tell the user a possible reason */ if(this->read_failcount==5){ _x_message(this->stream,1,"DVB Signal Lost. Please check connections."); } return total; } static buf_element_t *dvb_plugin_read_block (input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t todo) { /* dvb_input_plugin_t *this = (dvb_input_plugin_t *) this_gen; */ buf_element_t *buf = fifo->buffer_pool_alloc (fifo); int total_bytes; buf->content = buf->mem; buf->type = BUF_DEMUX_BLOCK; total_bytes = dvb_plugin_read (this_gen, buf->content, todo); if (total_bytes != todo) { buf->free_buffer (buf); return NULL; } buf->size = total_bytes; return buf; } static off_t dvb_plugin_seek (input_plugin_t *this_gen, off_t offset, int origin) { dvb_input_plugin_t *this = (dvb_input_plugin_t *) this_gen; xprintf(this->class->xine,XINE_VERBOSITY_DEBUG,"seek %lld bytes, origin %d\n", offset, origin); /* only relative forward-seeking is implemented */ if ((origin == SEEK_CUR) && (offset >= 0)) { for (;((int)offset) - BUFSIZE > 0; offset -= BUFSIZE) { this->curpos += dvb_plugin_read (this_gen, this->seek_buf, BUFSIZE); } this->curpos += dvb_plugin_read (this_gen, this->seek_buf, offset); } return this->curpos; } static off_t dvb_plugin_get_length (input_plugin_t *this_gen) { return 0; } static uint32_t dvb_plugin_get_capabilities (input_plugin_t *this_gen) { return 0; /* INPUT_CAP_CHAPTERS */ /* where did INPUT_CAP_AUTOPLAY go ?!? */ } static uint32_t dvb_plugin_get_blocksize (input_plugin_t *this_gen) { return 0; } static off_t dvb_plugin_get_current_pos (input_plugin_t *this_gen){ dvb_input_plugin_t *this = (dvb_input_plugin_t *) this_gen; return this->curpos; } static void dvb_plugin_dispose (input_plugin_t *this_gen) { dvb_input_plugin_t *this = (dvb_input_plugin_t *) this_gen; if (this->fd != -1) { close(this->fd); this->fd = -1; } if (this->nbc) { nbc_close (this->nbc); this->nbc = NULL; } if (this->event_queue) xine_event_dispose_queue (this->event_queue); if(this->mrl) free (this->mrl); if (this->channels) free (this->channels); if(this->eitbuffer) free (this->eitbuffer); if (this->tuner) tuner_dispose (this->tuner); if(this->proginfo_osd) this->stream->osd_renderer->hide (this->proginfo_osd,0); if(this->background) this->stream->osd_renderer->hide (this->background,0); /* free all memory associated with our OSD */ if(this->rec_osd) this->stream->osd_renderer->free_object(this->rec_osd); if(this->channel_osd) this->stream->osd_renderer->free_object(this->channel_osd); if(this->name_osd) this->stream->osd_renderer->free_object(this->name_osd); if(this->paused_osd) this->stream->osd_renderer->free_object(this->paused_osd); if(this->proginfo_osd) this->stream->osd_renderer->free_object(this->proginfo_osd); if(this->background) this->stream->osd_renderer->free_object(this->background); free (this); } static char* dvb_plugin_get_mrl (input_plugin_t *this_gen) { dvb_input_plugin_t *this = (dvb_input_plugin_t *) this_gen; return this->mrl; } static int dvb_plugin_get_optional_data (input_plugin_t *this_gen, void *data, int data_type) { return INPUT_OPTIONAL_UNSUPPORTED; } /* allow center cutout zoom for dvb content */ static void dvb_zoom_cb (void *this_gen, xine_cfg_entry_t *cfg) { dvb_input_plugin_t *this = (dvb_input_plugin_t *) this_gen; this->zoom_ok = cfg->num_value; if (!this) return; if (this->zoom_ok) { this->stream->video_out->set_property (this->stream->video_out, VO_PROP_ZOOM_X, 133); this->stream->video_out->set_property (this->stream->video_out, VO_PROP_ZOOM_Y, 133); } else { this->stream->video_out->set_property (this->stream->video_out, VO_PROP_ZOOM_X, 100); this->stream->video_out->set_property (this->stream->video_out, VO_PROP_ZOOM_Y, 100); } } static int dvb_plugin_open(input_plugin_t * this_gen) { dvb_input_plugin_t *this = (dvb_input_plugin_t *) this_gen; tuner_t *tuner; channel_t *channels; int num_channels; char str[256]; char *ptr; int x; char dummy=0; xine_cfg_entry_t zoomdvb; config_values_t *config = this->stream->xine->config; xine_cfg_entry_t lastchannel; xine_cfg_entry_t adapter; xine_config_lookup_entry(this->stream->xine, "input.dvb_adapternum", &adapter); if (!(tuner = tuner_init(this->class->xine,adapter.num_value))) { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: cannot open dvb device\n")); return 0; } if (strncasecmp(this->mrl, "dvb://", 6) == 0) { /* * This is either dvb:// * or the "magic" dvb:// * We load the channels from ~/.xine/channels.conf * and assume that its format is valid for our tuner type */ if (!(channels = load_channels(this, &num_channels, tuner->feinfo.type))) { /* failed to load the channels */ tuner_dispose(tuner); return 0; } if ((sscanf(this->mrl, "dvb://%d%1c", &this->channel, &dummy) >0 ) && ((isalpha(dummy)==0) && (isspace(dummy)==0))) { /* dvb:// format: load channels from ~/.xine/channels.conf */ if (this->channel >= num_channels) { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: channel %d out of range, defaulting to 0\n"), this->channel); this->channel = 0; } } else { /* dvb:// format ? */ char *channame = this->mrl + 6; if (*channame) { /* try to find the specified channel */ int idx = 0; xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: searching for channel %s\n"), channame); while (idx < num_channels) { if (strcasecmp(channels[idx].name, channame) == 0) break; idx++; } if (idx < num_channels) { this->channel = idx; } else { /* * try a partial match too * be smart and compare starting from the first char, then from * the second etc.. * Yes, this is expensive, but it happens really often * that the channels have really ugly names, sometimes prefixed * by numbers... */ int chanlen = strlen(channame); int offset = 0; xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: exact match for %s not found: trying partial matches\n"), channame); do { idx = 0; while (idx < num_channels) { if (strlen(channels[idx].name) > offset) { if (strncasecmp(channels[idx].name + offset, channame, chanlen) == 0) { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: found matching channel %s\n"), channels[idx].name); break; } } idx++; } offset++; xprintf(this->class->xine,XINE_VERBOSITY_LOG,"%d,%d,%d\n", offset, idx, num_channels); } while ((offset < 6) && (idx == num_channels)); if (idx < num_channels) { this->channel = idx; } else { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: channel %s not found in channels.conf, defaulting to channel 0\n"), channame); this->channel = 0; } } } else { /* just default to channel 0 */ xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: invalid channel specification, defaulting to channel 0\n")); this->channel = 0; } } } else if (strncasecmp(this->mrl, "dvbs://", 7) == 0) { /* * This is dvbs://: */ if (tuner->feinfo.type != FE_QPSK) { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: dvbs mrl specified but the tuner doesn't appear to be QPSK (DVB-S)\n")); tuner_dispose(tuner); return 0; } ptr = this->mrl; ptr += 7; channels = xine_xmalloc(sizeof(channel_t)); if (extract_channel_from_string(channels, ptr, tuner->feinfo.type) < 0) { free(channels); tuner_dispose(tuner); return 0; } this->channel = 0; } else if (strncasecmp(this->mrl, "dvbt://", 7) == 0) { /* * This is dvbt://: */ if (tuner->feinfo.type != FE_OFDM) { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: dvbt mrl specified but the tuner doesn't appear to be OFDM (DVB-T)\n")); tuner_dispose(tuner); return 0; } ptr = this->mrl; ptr += 7; channels = xine_xmalloc(sizeof(channel_t)); if (extract_channel_from_string(channels, ptr, tuner->feinfo.type) < 0) { free(channels); tuner_dispose(tuner); return 0; } this->channel = 0; } else if (strncasecmp(this->mrl, "dvbc://", 7) == 0) { /* * This is dvbc://: */ if (tuner->feinfo.type != FE_QAM) { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: dvbc mrl specified but the tuner doesn't appear to be QAM (DVB-C)\n")); tuner_dispose(tuner); return 0; } ptr = this->mrl; ptr += 7; channels = xine_xmalloc(sizeof(channel_t)); if (extract_channel_from_string(channels, ptr, tuner->feinfo.type) < 0) { free(channels); tuner_dispose(tuner); return 0; } this->channel = 0; }else { /* not our mrl */ tuner_dispose(tuner); return 0; } this->tuner = tuner; this->channels = channels; this->num_channels = num_channels; if (!tuner_set_channel(this, &this->channels[this->channel])) { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: tuner_set_channel failed\n")); return 0; } if ((this->fd = open(this->tuner->dvr_device, O_RDONLY |O_NONBLOCK)) < 0) { xprintf(this->class->xine, XINE_VERBOSITY_LOG, _("input_dvb: cannot open dvr device '%s'\n"), this->tuner->dvr_device); return 0; } /* now read the pat,find all accociated PIDs and add them to the stream */ dvb_parse_si(this); this->curpos = 0; this->osd = NULL; pthread_mutex_init(&this->mutex, NULL); this->event_queue = xine_event_new_queue(this->stream); this->eitbuffer=xine_xmalloc(2048*this->num_channels); /* * this osd is used to draw the "recording" sign */ this->rec_osd = this->stream->osd_renderer->new_object(this->stream->osd_renderer, 900, 61); this->stream->osd_renderer->set_position(this->rec_osd, 20, 10); this->stream->osd_renderer->set_font(this->rec_osd, "cetus", 26); this->stream->osd_renderer->set_encoding(this->rec_osd, NULL); this->stream->osd_renderer->set_text_palette(this->rec_osd, XINE_TEXTPALETTE_YELLOW_BLACK_TRANSPARENT, OSD_TEXT3); /* * this osd is used to draw the channel switching OSD */ this->channel_osd = this->stream->osd_renderer->new_object(this->stream->osd_renderer, 900, 600); this->stream->osd_renderer->set_position(this->channel_osd, 20, 10); this->stream->osd_renderer->set_font(this->channel_osd, "cetus", 26); this->stream->osd_renderer->set_encoding(this->channel_osd, NULL); this->stream->osd_renderer->set_text_palette(this->channel_osd, XINE_TEXTPALETTE_WHITE_NONE_TRANSLUCID, OSD_TEXT3); /* * this osd is for displaying currently shown channel name */ this->name_osd = this->stream->osd_renderer->new_object(this->stream->osd_renderer, 301, 61); this->stream->osd_renderer->set_position(this->name_osd, 20, 10); this->stream->osd_renderer->set_font(this->name_osd, "cetus", 40); this->stream->osd_renderer->set_encoding(this->name_osd, NULL); this->stream->osd_renderer->set_text_palette(this->name_osd, XINE_TEXTPALETTE_YELLOW_BLACK_TRANSPARENT, OSD_TEXT3); /* * this osd is for displaying Recording Paused */ this->paused_osd = this->stream->osd_renderer->new_object(this->stream->osd_renderer, 301, 161); this->stream->osd_renderer->set_position(this->paused_osd, 10, 50); this->stream->osd_renderer->set_font(this->paused_osd, "cetus", 40); this->stream->osd_renderer->set_encoding(this->paused_osd, NULL); this->stream->osd_renderer->set_text_palette(this->paused_osd, XINE_TEXTPALETTE_YELLOW_BLACK_TRANSPARENT, OSD_TEXT3); /* * this osd is for displaying Program Information (EIT) */ this->proginfo_osd = this->stream->osd_renderer->new_object(this->stream->osd_renderer, 1000, 600); this->stream->osd_renderer->set_position(this->proginfo_osd, 10, 10); this->stream->osd_renderer->set_font(this->proginfo_osd, "sans", 24); this->stream->osd_renderer->set_encoding(this->proginfo_osd, NULL); this->stream->osd_renderer->set_text_palette(this->proginfo_osd, XINE_TEXTPALETTE_WHITE_NONE_TRANSLUCID, OSD_TEXT3); this->stream->osd_renderer->set_text_palette(this->proginfo_osd, XINE_TEXTPALETTE_YELLOW_BLACK_TRANSPARENT, OSD_TEXT4); this->background = this->stream->osd_renderer->new_object(this->stream->osd_renderer, 1000, 600); this->stream->osd_renderer->set_position(this->background, 1, 1); this->stream->osd_renderer->set_font(this->background, "cetus", 32); this->stream->osd_renderer->set_encoding(this->background, NULL); this->stream->osd_renderer->set_text_palette(this->background, XINE_TEXTPALETTE_YELLOW_BLACK_TRANSPARENT, OSD_TEXT3); this->stream->osd_renderer->filled_rect(this->background, 1, 1, 1000, 600, 4); this->displaying=0; /* zoom for 4:3 in a 16:9 window */ config->register_bool(config, "media.dvb.zoom", 0, _("use DVB 'center cutout' (zoom)"), _("This will allow fullscreen " "playback of 4:3 content " "transmitted in a 16:9 frame."), 0, &dvb_zoom_cb, (void *) this); if (xine_config_lookup_entry(this->stream->xine, "media.dvb.zoom", &zoomdvb)) dvb_zoom_cb((input_plugin_t *) this, &zoomdvb); if (xine_config_lookup_entry(this->stream->xine, "media.dvb.remember_channel", &lastchannel)) if (lastchannel.num_value){ /* Remember last watched channel. never show this entry*/ config->update_num(config, "media.dvb.last_channel", this->channel+1); } /* * init metadata (channel title) */ snprintf(str, 256, "%04d - %s", this->channel, this->channels[this->channel].name); _x_meta_info_set(this->stream, XINE_META_INFO_TITLE, str); /* compute CRC table for rebuilding pat */ ts_build_crc32_table(this); /* Clear all pids, the pmt will tell us which to use */ for (x = 0; x < MAXFILTERS; x++){ this->channels[this->channel].pid[x] = NOPID; } return 1; } static input_plugin_t *dvb_class_get_instance (input_class_t *class_gen, xine_stream_t *stream, const char *data) { dvb_input_class_t *class = (dvb_input_class_t *) class_gen; dvb_input_plugin_t *this; char *mrl = (char *) data; if(strncasecmp (mrl, "dvb://",6)) if(strncasecmp(mrl,"dvbs://",7)) if(strncasecmp(mrl,"dvbt://",7)) if(strncasecmp(mrl,"dvbc://",7)) return NULL; this = (dvb_input_plugin_t *) xine_xmalloc (sizeof(dvb_input_plugin_t)); this->stream = stream; this->mrl = strdup(mrl); this->class = class; this->tuner = NULL; this->channels = NULL; this->fd = -1; this->nbc = nbc_init (this->stream); this->osd = NULL; this->event_queue = NULL; this->record_fd = -1; this->read_failcount = 0; this->input_plugin.open = dvb_plugin_open; this->input_plugin.get_capabilities = dvb_plugin_get_capabilities; this->input_plugin.read = dvb_plugin_read; this->input_plugin.read_block = dvb_plugin_read_block; this->input_plugin.seek = dvb_plugin_seek; this->input_plugin.get_current_pos = dvb_plugin_get_current_pos; this->input_plugin.get_length = dvb_plugin_get_length; this->input_plugin.get_blocksize = dvb_plugin_get_blocksize; this->input_plugin.get_mrl = dvb_plugin_get_mrl; this->input_plugin.get_optional_data = dvb_plugin_get_optional_data; this->input_plugin.dispose = dvb_plugin_dispose; this->input_plugin.input_class = class_gen; return &this->input_plugin; } /* * dvb input plugin class stuff */ static char *dvb_class_get_description (input_class_t *this_gen) { return _("DVB (Digital TV) input plugin"); } static const char *dvb_class_get_identifier (input_class_t *this_gen) { return "dvb"; } static void dvb_class_dispose(input_class_t * this_gen) { dvb_input_class_t *class = (dvb_input_class_t *) this_gen; int x; for(x=0;xnumchannels;x++) free(class->autoplaylist[x]); free(class); } static int dvb_class_eject_media (input_class_t *this_gen) { return 1; } static char **dvb_class_get_autoplay_list(input_class_t * this_gen, int *num_files) { dvb_input_class_t *class = (dvb_input_class_t *) this_gen; channel_t *channels=NULL; FILE *f; char *tmpbuffer=xine_xmalloc(BUFSIZE); char *foobuffer=xine_xmalloc(BUFSIZE); char *str=tmpbuffer; int num_channels; int nlines=0; int default_channel; xine_cfg_entry_t lastchannel_enable; xine_cfg_entry_t lastchannel; snprintf(tmpbuffer, BUFSIZE, "%s/.xine/channels.conf", xine_get_homedir()); num_channels = 0; f=fopen (tmpbuffer,"rb"); if(!f){ /* channels.conf not found in .xine */ class->mrls[0]="Sorry, No channels.conf found"; class->mrls[1]="Please run the dvbscan utility"; class->mrls[2]="from the dvb drivers apps package"; class->mrls[3]="and place the file in ~/.xine/"; *num_files=4; return class->mrls; } else { while (fgets(str, BUFSIZE, f)) nlines++; } fclose (f); if (xine_config_lookup_entry(class->xine, "media.dvb.remember_channel", &lastchannel_enable)) if (lastchannel_enable.num_value){ num_channels++; if (xine_config_lookup_entry(class->xine, "media.dvb.last_channel", &lastchannel)) default_channel = lastchannel.num_value; } if (nlines+lastchannel_enable.num_value >= MAX_AUTOCHANNELS) nlines = MAX_AUTOCHANNELS-lastchannel_enable.num_value; snprintf(tmpbuffer, BUFSIZE, "%s/.xine/channels.conf", xine_get_homedir()); f=fopen (tmpbuffer,"rb"); channels=xine_xmalloc(sizeof(channel_t)*(nlines+lastchannel_enable.num_value)); while (fgets(str,BUFSIZE,f) && num_channels < nlines+lastchannel_enable.num_value) { if (extract_channel_from_string (&(channels[num_channels]), str, 0) < 0) continue; sprintf(foobuffer,"dvb://%s",channels[num_channels].name); if(class->autoplaylist[num_channels]) free(class->autoplaylist[num_channels]); class->autoplaylist[num_channels]=xine_xmalloc(128); class->autoplaylist[num_channels]=strdup(foobuffer); num_channels++; } if (lastchannel_enable.num_value){ if (lastchannel.num_value>-1) /* plugin has been used before - channel is valid */ sprintf(foobuffer,"dvb://%s",channels[lastchannel.num_value].name); else /* set a reasonable default - the first channel */ sprintf(foobuffer,"dvb://%s",channels[lastchannel_enable.num_value].name); if(class->autoplaylist[0]) free(class->autoplaylist[0]); class->autoplaylist[0]=xine_xmalloc(128); class->autoplaylist[0]=strdup(foobuffer); } free(tmpbuffer); free(foobuffer); free(channels); fclose(f); *num_files = num_channels; class->numchannels=nlines; return class->autoplaylist; } static void *init_class (xine_t *xine, void *data) { dvb_input_class_t *this; config_values_t *config = xine->config; this = (dvb_input_class_t *) xine_xmalloc (sizeof (dvb_input_class_t)); this->xine = xine; this->input_class.get_instance = dvb_class_get_instance; this->input_class.get_identifier = dvb_class_get_identifier; this->input_class.get_description = dvb_class_get_description; this->input_class.get_dir = NULL; this->input_class.get_autoplay_list = dvb_class_get_autoplay_list; this->input_class.dispose = dvb_class_dispose; this->input_class.eject_media = dvb_class_eject_media; this->mrls[0] = "dvb://"; this->mrls[1] = "dvbs://"; this->mrls[2] = "dvbc://"; this->mrls[3] = "dvbt://"; this->mrls[4] = 0; xprintf(this->xine,XINE_VERBOSITY_DEBUG,"init class succeeded\n"); /* Enable remembering of last watched channel */ config->register_bool(config, "media.dvb.remember_channel", 1, _("Remember last DVB channel watched"), _("On autoplay, xine will remember and " "switch to this channel. "), 0, NULL, NULL); /* Enable remembering of last watched channel never show this entry*/ config->register_num(config, "media.dvb.last_channel", -1, _("Remember last DVB channel watched"), _("If enabled, xine will remember and " "switch to this channel. "), 11, NULL, NULL); config->register_num(config, "input.dvb_adapternum", 0, _("Number of dvb card to use."), _("Leave this at zero unless you " "really have more than 1 card " "in your system."), 0, NULL, (void *) this); return this; } /* * exported plugin catalog entry */ plugin_info_t xine_plugin_info[] = { /* type, API, "name", version, special_info, init_function */ { PLUGIN_INPUT, 15, "DVB", XINE_VERSION_CODE, NULL, init_class }, { PLUGIN_NONE, 0, "", 0, NULL, NULL } };