You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1924 lines
66 KiB

/*****************************************************************************
* scan.c: DVB scanner helpers
*****************************************************************************
* Copyright (C) 2008,2010 VLC authors and VideoLAN
*
* Authors: Laurent Aimar <fenrir@videolan.org>
* David Kaplan <david@2of1.org>
* Ilkka Ollakka <ileoo@videolan.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_block.h>
#include <vlc_dialog.h>
#include <vlc_charset.h>
#include <sys/types.h>
/* Include dvbpsi headers */
#include <dvbpsi/dvbpsi.h>
#include <dvbpsi/descriptor.h>
#include <dvbpsi/pat.h>
#include <dvbpsi/dr.h>
#include <dvbpsi/demux.h>
#include <dvbpsi/sdt.h>
#include <dvbpsi/nit.h>
#include "dvb.h"
#include "scan.h"
#include "scan_list.h"
#include "../../demux/dvb-text.h"
#include "../../mux/mpeg/dvbpsi_compat.h"
#define PSI_PAT_PID 0x00
#define SI_NIT_PID 0x10
#define SI_SDT_PID 0x11
#define NIT_CURRENT_NETWORK_TABLE_ID 0x40
#define NIT_OTHER_NETWORK_TABLE_ID 0x41
#define SDT_CURRENT_TS_TABLE_ID 0x42
#define SDT_OTHER_TS_TABLE_ID 0x46
#define NETWORK_ID_RESERVED 0x0000
typedef enum
{
SERVICE_TYPE_RESERVED = 0x00,
SERVICE_TYPE_DIGITAL_TELEVISION = 0x01,
SERVICE_TYPE_DIGITAL_RADIO = 0x02,
SERVICE_TYPE_DIGITAL_MPEG2_HD = 0x11,
SERVICE_TYPE_DIGITAL_TELEVISION_AC_SD = 0x16,
SERVICE_TYPE_DIGITAL_TELEVISION_AC_HD = 0x19,
SERVICE_TYPE_DIGITAL_RADIO_AC = 0x0A,
} scan_service_type_t;
typedef struct scan_multiplex_t scan_multiplex_t;
struct scan_service_t
{
const scan_multiplex_t *p_mplex; /* multiplex reference */
const void * stickyref; /* Callee private storage across updates */
uint16_t i_original_network_id;
uint16_t i_program; /* program number (service id) */
scan_service_type_t type;
char *psz_name; /* channel name in utf8 */
char *psz_provider; /* service provider */
uint16_t i_channel; /* logical channel number */
bool b_crypted; /* True if potentially encrypted */
char *psz_original_network_name;
};
struct scan_multiplex_t
{
scan_tuner_config_t cfg;
uint16_t i_network_id;
uint16_t i_ts_id;
char *psz_network_name;
size_t i_services;
scan_service_t **pp_services;
int i_snr;
bool b_scanned;
uint8_t i_nit_version;
uint8_t i_sdt_version;
};
typedef struct
{
scan_modulation_t modulation;
unsigned i_symbolrate_index;
unsigned i_index;
} scan_enumeration_t;
struct scan_t
{
vlc_object_t *p_obj;
scan_frontend_tune_cb pf_tune;
scan_demux_filter_cb pf_filter;
scan_frontend_stats_cb pf_stats;
scan_demux_read_cb pf_read;
scan_service_notify_cb pf_notify_service;
void *p_cbdata;
vlc_dialog_id *p_dialog_id;
scan_parameter_t parameter;
int64_t i_time_start;
size_t i_multiplex_toscan;
size_t i_multiplex;
scan_multiplex_t **pp_multiplex;
bool b_multiplexes_from_nit;
scan_list_entry_t *p_scanlist;
size_t i_scanlist;
const scan_list_entry_t *p_current;
scan_enumeration_t spectrum;
};
typedef struct
{
vlc_object_t *p_obj;
scan_tuner_config_t cfg;
int i_snr;
struct
{
dvbpsi_pat_t *p_pat;
dvbpsi_sdt_t *p_sdt;
dvbpsi_nit_t *p_nit;
} local;
struct
{
dvbpsi_sdt_t **pp_sdt;
size_t i_sdt;
dvbpsi_nit_t **pp_nit;
size_t i_nit;
} others;
scan_type_t type;
bool b_use_nit;
uint16_t i_nit_pid;
dvbpsi_t *p_pathandle;
dvbpsi_t *p_sdthandle;
dvbpsi_t *p_nithandle;
} scan_session_t;
static scan_session_t * scan_session_New( scan_t *p_scan, const scan_tuner_config_t *p_cfg );
static void scan_session_Destroy( scan_t *p_scan, scan_session_t *p_session );
static bool scan_session_Push( scan_session_t *p_scan, const uint8_t *p_packet );
static unsigned scan_session_GetTablesTimeout( const scan_session_t *p_session );
/* */
static void scan_tuner_config_Init( scan_tuner_config_t *p_cfg, const scan_parameter_t *p_params )
{
memset( p_cfg, 0, sizeof(*p_cfg) );
p_cfg->coderate_lp = SCAN_CODERATE_AUTO;
p_cfg->coderate_hp = SCAN_CODERATE_AUTO;
p_cfg->inner_fec = SCAN_CODERATE_AUTO;
switch(p_params->type)
{
case SCAN_DVB_T: p_cfg->delivery = SCAN_DELIVERY_DVB_T; break;
case SCAN_DVB_S: p_cfg->delivery = SCAN_DELIVERY_DVB_S; break;
case SCAN_DVB_C: p_cfg->delivery = SCAN_DELIVERY_DVB_C; break;
default: p_cfg->delivery = SCAN_DELIVERY_UNKNOWN; break;
}
p_cfg->type = p_params->type;
}
static bool scan_tuner_config_StandardValidate( const scan_tuner_config_t *p_cfg )
{
if( p_cfg->i_frequency == 0 ||
p_cfg->i_frequency == UINT32_MAX / 10 ) /* Invalid / broken transponder info on French TNT */
return false;
if( p_cfg->type == SCAN_DVB_T && p_cfg->i_bandwidth == 0 )
return false;
return true;
}
static scan_service_t *scan_service_New( uint16_t i_program )
{
scan_service_t *p_srv = malloc( sizeof(*p_srv) );
if( !p_srv )
return NULL;
p_srv->p_mplex = NULL;
p_srv->stickyref = NULL;
p_srv->i_program = i_program;
p_srv->i_original_network_id = NETWORK_ID_RESERVED;
p_srv->type = SERVICE_TYPE_RESERVED;
p_srv->psz_name = NULL;
p_srv->psz_provider = NULL;
p_srv->psz_original_network_name = NULL;
p_srv->i_channel = -1;
p_srv->b_crypted = false;
return p_srv;
}
static void scan_service_Delete( scan_service_t *p_srv )
{
free( p_srv->psz_original_network_name );
free( p_srv->psz_name );
free( p_srv->psz_provider );
free( p_srv );
}
static uint32_t decode_BCD( uint32_t input )
{
uint32_t output = 0;
for( short index=28; index >= 0 ; index -= 4 )
{
output *= 10;
output += ((input >> index) & 0x0f);
};
return output;
}
static int scan_service_type_Supported( scan_service_type_t service_type )
{
switch( service_type )
{
case SERVICE_TYPE_DIGITAL_TELEVISION:
case SERVICE_TYPE_DIGITAL_RADIO:
case SERVICE_TYPE_DIGITAL_MPEG2_HD:
case SERVICE_TYPE_DIGITAL_TELEVISION_AC_SD:
case SERVICE_TYPE_DIGITAL_TELEVISION_AC_HD:
case SERVICE_TYPE_DIGITAL_RADIO_AC:
return true;
default:
break;
}
return false;
}
static scan_multiplex_t *scan_multiplex_New( const scan_tuner_config_t *p_cfg, uint16_t i_ts_id )
{
scan_multiplex_t *p_mplex = malloc( sizeof(*p_mplex) );
if( likely(p_mplex) )
{
p_mplex->cfg = *p_cfg;
p_mplex->i_ts_id = i_ts_id;
p_mplex->i_network_id = NETWORK_ID_RESERVED;
p_mplex->psz_network_name = NULL;
p_mplex->i_services = 0;
p_mplex->pp_services = NULL;
p_mplex->i_nit_version = UINT8_MAX;
p_mplex->i_sdt_version = UINT8_MAX;
p_mplex->i_snr = -1;
p_mplex->b_scanned = false;
}
return p_mplex;
}
static void scan_multiplex_Clean( scan_multiplex_t *p_mplex )
{
for( size_t i=0; i<p_mplex->i_services; i++ )
scan_service_Delete( p_mplex->pp_services[i] );
free( p_mplex->pp_services );
free( p_mplex->psz_network_name );
}
static void scan_multiplex_Delete( scan_multiplex_t *p_mplex )
{
scan_multiplex_Clean( p_mplex );
free( p_mplex );
}
static bool scan_multiplex_AddService( scan_multiplex_t *p_mplex, scan_service_t *p_service )
{
if( unlikely(p_service->p_mplex) ) /* Already belongs to another multiplex, should never happen */
return false;
scan_service_t **pp_realloc = realloc( p_mplex->pp_services,
sizeof(scan_service_t *) * (p_mplex->i_services + 1) );
if( unlikely(!pp_realloc) )
return false;
pp_realloc[p_mplex->i_services] = p_service;
p_mplex->pp_services = pp_realloc;
p_mplex->i_services++;
p_service->p_mplex = p_mplex;
return true;
}
static scan_service_t * scan_multiplex_FindService( const scan_multiplex_t *p_mplex, uint16_t i_program )
{
for( size_t i = 0; i < p_mplex->i_services; i++ )
{
if( p_mplex->pp_services[i]->i_program == i_program )
return p_mplex->pp_services[i];
}
return NULL;
}
void scan_parameter_Init( scan_parameter_t *p_dst )
{
memset( p_dst, 0, sizeof(*p_dst) );
}
void scan_parameter_Clean( scan_parameter_t *p_dst )
{
free( p_dst->psz_scanlist_file );
}
static void scan_parameter_Copy( const scan_parameter_t *p_src, scan_parameter_t *p_dst )
{
scan_parameter_Clean( p_dst );
*p_dst = *p_src;
if( p_src->psz_scanlist_file )
p_dst->psz_scanlist_file = strdup( p_src->psz_scanlist_file );
}
static void scan_Prepare( vlc_object_t *p_obj, const scan_parameter_t *p_parameter, scan_t *p_scan )
{
if( p_parameter->type == SCAN_DVB_S &&
p_parameter->psz_scanlist_file && p_parameter->scanlist_format == FORMAT_DVBv3 )
{
p_scan->p_scanlist =
scan_list_dvbv3_load( p_obj, p_parameter->psz_scanlist_file, &p_scan->i_scanlist );
if( p_scan->p_scanlist )
msg_Dbg( p_scan->p_obj, "using satellite config file (%s)", p_parameter->psz_scanlist_file );
}
else if( p_parameter->psz_scanlist_file &&
p_parameter->scanlist_format == FORMAT_DVBv5 )
{
if( p_parameter->type == SCAN_DVB_T )
{
p_scan->p_scanlist = scan_list_dvbv5_load( p_obj,
p_parameter->psz_scanlist_file,
&p_scan->i_scanlist );
}
}
}
static void scan_Debug_Parameters( vlc_object_t *p_obj, const scan_parameter_t *p_parameter )
{
const char rgc_types[3] = {'T', 'S', 'C' };
if( !p_parameter->type )
return;
msg_Dbg( p_obj, "DVB-%c scanning:", rgc_types[ p_parameter->type - 1 ] );
if( p_parameter->type != SCAN_DVB_S )
{
msg_Dbg( p_obj, " - frequency [%d, %d]",
p_parameter->frequency.i_min, p_parameter->frequency.i_max );
msg_Dbg( p_obj, " - bandwidth [%d,%d]",
p_parameter->bandwidth.i_min, p_parameter->bandwidth.i_max );
msg_Dbg( p_obj, " - exhaustive mode %s", p_parameter->b_exhaustive ? "on" : "off" );
}
if( p_parameter->type == SCAN_DVB_C )
msg_Dbg( p_obj, " - scannin modulations %s", p_parameter->b_modulation_set ? "off" : "on" );
if( p_parameter->type == SCAN_DVB_S && p_parameter->psz_scanlist_file )
msg_Dbg( p_obj, " - satellite [%s]", p_parameter->psz_scanlist_file );
msg_Dbg( p_obj, " - use NIT %s", p_parameter->b_use_nit ? "on" : "off" );
msg_Dbg( p_obj, " - FTA only %s", p_parameter->b_free_only ? "on" : "off" );
}
/* */
scan_t *scan_New( vlc_object_t *p_obj, const scan_parameter_t *p_parameter,
scan_frontend_tune_cb pf_frontend,
scan_frontend_stats_cb pf_status,
scan_demux_filter_cb pf_filter,
scan_demux_read_cb pf_read,
void *p_cbdata )
{
if( p_parameter->type == SCAN_NONE )
return NULL;
scan_t *p_scan = malloc( sizeof( *p_scan ) );
if( unlikely(p_scan == NULL) )
return NULL;
p_scan->p_obj = VLC_OBJECT(p_obj);
p_scan->pf_tune = pf_frontend;
p_scan->pf_stats = pf_status;
p_scan->pf_read = pf_read;
p_scan->pf_filter = pf_filter;
p_scan->pf_notify_service = NULL;
p_scan->p_cbdata = p_cbdata;
p_scan->p_dialog_id = NULL;
p_scan->i_multiplex = 0;
p_scan->pp_multiplex = NULL;
p_scan->i_multiplex_toscan = 0;
p_scan->b_multiplexes_from_nit = false;
scan_parameter_Init( &p_scan->parameter );
scan_parameter_Copy( p_parameter, &p_scan->parameter );
p_scan->i_time_start = vlc_tick_now();
p_scan->p_scanlist = NULL;
p_scan->i_scanlist = 0;
scan_Prepare( p_obj, p_parameter, p_scan );
p_scan->p_current = p_scan->p_scanlist;
p_scan->spectrum.i_index = 0;
p_scan->spectrum.i_symbolrate_index = 0;
p_scan->spectrum.modulation = 0;
scan_Debug_Parameters( p_obj, p_parameter );
return p_scan;
}
void scan_Destroy( scan_t *p_scan )
{
if( !p_scan )
return;
if( p_scan->p_dialog_id != NULL )
vlc_dialog_release( p_scan->p_obj, p_scan->p_dialog_id );
scan_parameter_Clean( &p_scan->parameter );
for( size_t i = 0; i < p_scan->i_multiplex; i++ )
scan_multiplex_Delete( p_scan->pp_multiplex[i] );
free( p_scan->pp_multiplex );
scan_list_entries_release( p_scan->p_scanlist );
free( p_scan );
}
static void scan_SetMultiplexScanStatus( scan_t *p_scan, scan_multiplex_t *p_mplex, bool b_scanned )
{
if( p_mplex->b_scanned != b_scanned )
{
p_mplex->b_scanned = b_scanned;
p_scan->i_multiplex_toscan += ( b_scanned ) ? -1 : 1;
}
}
static bool scan_AddMultiplex( scan_t *p_scan, scan_multiplex_t *p_mplex )
{
scan_multiplex_t **pp_realloc = realloc( p_scan->pp_multiplex,
sizeof(scan_multiplex_t *) * (p_scan->i_multiplex + 1) );
if( unlikely(!pp_realloc) )
return false;
pp_realloc[p_scan->i_multiplex] = p_mplex;
p_scan->pp_multiplex = pp_realloc;
p_scan->i_multiplex++;
if( !p_mplex->b_scanned )
p_scan->i_multiplex_toscan++;
return true;
}
static scan_multiplex_t * scan_FindMultiplex( const scan_t *p_scan, uint16_t i_ts_id )
{
for( size_t i = 0; i < p_scan->i_multiplex; i++ )
{
if( p_scan->pp_multiplex[i]->i_ts_id == i_ts_id )
return p_scan->pp_multiplex[i];
}
return NULL;
}
static scan_multiplex_t *scan_FindOrCreateMultiplex( scan_t *p_scan, uint16_t i_ts_id,
const scan_tuner_config_t *p_cfg )
{
scan_multiplex_t *p_mplex = scan_FindMultiplex( p_scan, i_ts_id );
if( p_mplex == NULL )
{
p_mplex = scan_multiplex_New( p_cfg, i_ts_id );
if( likely(p_mplex) )
{
if ( unlikely(!scan_AddMultiplex( p_scan, p_mplex )) ) /* OOM */
{
scan_multiplex_Delete( p_mplex );
return NULL;
}
}
}
return p_mplex;
}
static size_t scan_CountServices( const scan_t *p_scan )
{
size_t i_total_services = 0;
for( size_t j = 0; j < p_scan->i_multiplex; j++ )
i_total_services += p_scan->pp_multiplex[j]->i_services;
return i_total_services;
}
static int Scan_Next_DVB_SpectrumExhaustive( const scan_parameter_t *p_params, scan_enumeration_t *p_spectrum,
scan_tuner_config_t *p_cfg, double *pf_pos )
{
unsigned i_bandwidth_count = p_params->bandwidth.i_max - p_params->bandwidth.i_min + 1;
unsigned i_frequency_step = p_params->frequency.i_step ? p_params->frequency.i_step : 166667;
unsigned i_frequency_count = (p_params->frequency.i_max - p_params->frequency.i_min) / p_params->frequency.i_step;
if( p_spectrum->i_index > i_frequency_count * i_bandwidth_count )
return VLC_EGENERIC;
const int i_bi = p_spectrum->i_index % i_bandwidth_count;
const int i_fi = p_spectrum->i_index / i_bandwidth_count;
p_cfg->i_frequency = p_params->frequency.i_min + i_fi * i_frequency_step;
p_cfg->i_bandwidth = p_params->bandwidth.i_min + i_bi;
*pf_pos = (double)p_spectrum->i_index / i_frequency_count;
p_spectrum->i_index++;
return VLC_SUCCESS;
}
static int Scan_Next_DVBC( const scan_parameter_t *p_params, scan_enumeration_t *p_spectrum,
scan_tuner_config_t *p_cfg, double *pf_pos )
{
bool b_rotate=true;
if( !p_params->b_modulation_set )
{
p_spectrum->modulation = (p_spectrum->modulation >> 1 );
/* if we iterated all modulations, move on */
/* dvb utils dvb-c channels files seems to have only
QAM64...QAM256, so lets just iterate over those */
if( p_spectrum->modulation < SCAN_MODULATION_QAM_64)
{
p_spectrum->modulation = SCAN_MODULATION_QAM_256;
} else {
b_rotate=false;
}
}
p_cfg->modulation = p_spectrum->modulation;
if( p_params->i_symbolrate == 0 )
{
/* symbol rates from dvb-tools dvb-c files */
static const unsigned short symbolrates[] = {
6900, 6875, 6950
/* With DR_44 we can cover other symbolrates from NIT-info
as all channel-seed files have at least one channel that
has one of these symbolrate
*/
};
enum { num_symbols = (sizeof(symbolrates)/sizeof(*symbolrates)) };
/* if we rotated modulations, rotate symbolrate */
if( b_rotate )
{
p_spectrum->i_symbolrate_index++;
p_spectrum->i_symbolrate_index %= num_symbols;
}
p_cfg->i_symbolrate = 1000 * (symbolrates[ p_spectrum->i_symbolrate_index ] );
if( p_spectrum->i_symbolrate_index )
b_rotate=false;
}
else
{
p_cfg->i_symbolrate = p_params->i_symbolrate;
}
if( p_params->b_exhaustive )
return Scan_Next_DVB_SpectrumExhaustive( p_params, p_spectrum, p_cfg, pf_pos );
/* Values taken from dvb-scan utils frequency-files, sorted by how
* often they appear. This hopefully speeds up finding services. */
static const unsigned int frequencies[] = { 41000, 39400, 40200,
38600, 41800, 36200, 44200, 43400, 37000, 35400, 42600, 37800,
34600, 45800, 45000, 46600, 32200, 51400, 49000, 33800, 31400,
30600, 47400, 71400, 69000, 68200, 58600, 56200, 54600, 49800,
48200, 33000, 79400, 72200, 69800, 67400, 66600, 65000, 64200,
61000, 55400, 53000, 52200, 50600, 29800, 16200, 15400, 11300,
78600, 77000, 76200, 75400, 74600, 73800, 73000, 70600, 57800,
57000, 53800, 12100, 81000, 77800, 65800, 63400, 61800, 29000,
17000, 85000, 84200, 83400, 81800, 80200, 59400, 36900, 28300,
26600, 25800, 25000, 24200, 23400, 85800, 74800, 73200, 72800,
72400, 72000, 66000, 65600, 60200, 42500, 41700, 40900, 40100,
39300, 38500, 37775, 37700, 37200, 36100, 35600, 35300, 34700,
34500, 33900, 33700, 32900, 32300, 32100, 31500, 31300, 30500,
29900, 29700, 29100, 28950, 28200, 28000, 27500, 27400, 27200,
26700, 25900, 25500, 25100, 24300, 24100, 23500, 23200, 22700,
22600, 21900, 21800, 21100, 20300, 19500, 18700, 17900, 17100,
16300, 15500, 14700, 14600, 14500, 14300, 13900, 13700, 13100,
12900, 12500, 12300
};
enum { num_frequencies = (sizeof(frequencies)/sizeof(*frequencies)) };
if( p_spectrum->i_index >= num_frequencies )
return VLC_EGENERIC; /* End */
p_cfg->i_frequency = 10000 * ( frequencies[ p_spectrum->i_index ] );
*pf_pos = (double)(p_spectrum->i_index * 1000 +
p_spectrum->i_symbolrate_index * 100 +
(256 - (p_spectrum->modulation >> 4)) )
/ (num_frequencies * 1000 + 900 + 16);
if( b_rotate )
p_spectrum->i_index++;
return VLC_SUCCESS;
}
static int Scan_Next_DVBT( const scan_parameter_t *p_params, scan_enumeration_t *p_spectrum,
scan_tuner_config_t *p_cfg, double *pf_pos )
{
if( p_params->b_exhaustive )
return Scan_Next_DVB_SpectrumExhaustive( p_params, p_spectrum, p_cfg, pf_pos );
unsigned i_frequency_step = p_params->frequency.i_step ? p_params->frequency.i_step : 166667;
unsigned i_bandwidth_min = p_params->bandwidth.i_min ? p_params->bandwidth.i_min : 6;
unsigned i_bandwidth_max = p_params->bandwidth.i_max ? p_params->bandwidth.i_max : 8;
unsigned i_bandwidth_count = i_bandwidth_max - i_bandwidth_min + 1;
static const int i_band_count = 2;
static const struct
{
const char *psz_name;
int i_min;
int i_max;
}
band[2] =
{
{ "VHF", 174, 230 },
{ "UHF", 470, 862 },
};
const int i_offset_count = 5;
const int i_mhz = 1000000;
/* We will probe the whole band divided in all bandwidth possibility trying
* i_offset_count offset around the position
*/
for( ;; p_spectrum->i_index++ )
{
const int i_bi = p_spectrum->i_index % i_bandwidth_count;
const int i_oi = (p_spectrum->i_index / i_bandwidth_count) % i_offset_count;
const int i_fi = (p_spectrum->i_index / i_bandwidth_count) / i_offset_count;
const int i_bandwidth = i_bandwidth_min + i_bi;
int i;
for( i = 0; i < i_band_count; i++ )
{
if( i_fi >= band[i].i_min && i_fi <= band[i].i_max )
break;
}
if( i >=i_band_count )
{
if( i_fi > band[i_band_count-1].i_max )
{
p_spectrum->i_index++;
return VLC_EGENERIC;
}
continue;
}
const unsigned i_frequency_min = band[i].i_min*i_mhz + i_bandwidth*i_mhz/2;
const unsigned i_frequency_base = i_fi*i_mhz;
if( i_frequency_base >= i_frequency_min && ( i_frequency_base - i_frequency_min ) % ( i_bandwidth*i_mhz ) == 0 )
{
const unsigned i_frequency = i_frequency_base + ( i_oi - i_offset_count/2 ) * i_frequency_step;
p_cfg->i_frequency = i_frequency;
p_cfg->i_bandwidth = i_bandwidth;
int i_current = 0, i_total = 0;
for( i = 0; i < i_band_count; i++ )
{
const int i_frag = band[i].i_max-band[i].i_min;
if( i_fi >= band[i].i_min )
i_current += __MIN( i_fi - band[i].i_min, i_frag );
i_total += i_frag;
}
*pf_pos = (double)( i_current + (double)i_oi / i_offset_count ) / i_total;
p_spectrum->i_index++;
return VLC_SUCCESS;
}
}
}
static int Scan_GetNextSpectrumTunerConfig( scan_t *p_scan, scan_tuner_config_t *p_cfg, double *pf_pos )
{
int i_ret = VLC_EGENERIC;
switch( p_scan->parameter.type )
{
case SCAN_DVB_T:
i_ret = Scan_Next_DVBT( &p_scan->parameter, &p_scan->spectrum, p_cfg, pf_pos );
break;
case SCAN_DVB_C:
i_ret = Scan_Next_DVBC( &p_scan->parameter, &p_scan->spectrum, p_cfg, pf_pos );
break;
default:
break;
}
return i_ret;
}
static int Scan_GetNextTunerConfig( scan_t *p_scan, scan_tuner_config_t *p_cfg, double *pf_pos )
{
/* Note: Do not forget to advance current scan (avoid frontend tuning errors loops ) */
if( p_scan->p_scanlist && p_scan->p_current )
{
const scan_list_entry_t *p_entry = p_scan->p_current;
p_cfg->i_frequency = p_entry->i_freq;
p_cfg->i_bandwidth = p_entry->i_bw / 1000000;
p_cfg->modulation = p_entry->modulation;
switch( p_entry->delivery )
{
case SCAN_DELIVERY_UNKNOWN:
break;
case SCAN_DELIVERY_DVB_T:
p_cfg->coderate_hp = p_entry->coderate_hp;
p_cfg->coderate_lp = p_entry->coderate_lp;
p_cfg->type = SCAN_DVB_T;
break;
case SCAN_DELIVERY_DVB_S:
p_cfg->type = SCAN_DVB_S;
p_cfg->polarization = p_entry->polarization;
p_cfg->i_symbolrate = p_entry->i_rate / 1000;
p_cfg->inner_fec = p_entry->inner_fec;
break;
case SCAN_DELIVERY_DVB_C:
p_cfg->type = SCAN_DVB_C;
p_cfg->i_symbolrate = p_entry->i_rate / 1000;
p_cfg->inner_fec = p_entry->inner_fec;
break;
default:
p_cfg->type = SCAN_NONE;
break;
}
p_scan->p_current = p_scan->p_current->p_next;
*pf_pos = (double) p_scan->spectrum.i_index++ / p_scan->i_scanlist;
return VLC_SUCCESS;
}
if( p_scan->p_scanlist == NULL &&
( p_scan->i_multiplex == 0 || /* Stop frequency scanning if we've found a valid NIT */
(p_scan->parameter.b_use_nit && !p_scan->b_multiplexes_from_nit) ) )
{
int i_ret = Scan_GetNextSpectrumTunerConfig( p_scan, p_cfg, pf_pos );
if( i_ret == VLC_SUCCESS )
return i_ret;
}
if( p_scan->i_multiplex_toscan )
{
for( size_t i=0; i<p_scan->i_multiplex_toscan; i++ )
{
if( !p_scan->pp_multiplex[i]->b_scanned )
{
scan_SetMultiplexScanStatus( p_scan, p_scan->pp_multiplex[i], true );
*p_cfg = p_scan->pp_multiplex[i]->cfg;
*pf_pos = (double) 1.0 - (p_scan->i_multiplex / p_scan->i_multiplex_toscan);
return VLC_SUCCESS;
}
}
}
return VLC_ENOENT;
}
static int scan_Next( scan_t *p_scan, scan_tuner_config_t *p_cfg )
{
double f_position;
int i_ret;
if( scan_IsCancelled( p_scan ) )
return VLC_EGENERIC;
//do
{
scan_tuner_config_Init( p_cfg, &p_scan->parameter );
i_ret = Scan_GetNextTunerConfig( p_scan, p_cfg, &f_position );
if( i_ret )
return i_ret;
}
//while( !scan_tuner_config_ParametersValidate( &p_scan->parameter, p_cfg ) );
const size_t i_total_services = scan_CountServices( p_scan );
const vlc_tick_t i_eta = f_position > 0.005 ? (vlc_tick_now() - p_scan->i_time_start) * ( 1.0 / f_position - 1.0 ) : -1;
char psz_eta[MSTRTIME_MAX_SIZE];
const char *psz_fmt = _("%.1f MHz (%d services)\n~%s remaining");
vlc_tick_to_str( psz_eta, i_eta );
if( i_eta >= 0 )
msg_Info( p_scan->p_obj, "Scan ETA %s | %f", psz_eta, f_position * 100 );
if( p_scan->p_dialog_id == NULL )
{
p_scan->p_dialog_id =
vlc_dialog_display_progress( p_scan->p_obj, false,
f_position, _("Cancel"),
_("Scanning DVB"), psz_fmt,
(double)p_cfg->i_frequency / 1000000,
i_total_services,
psz_eta);
}
else
{
vlc_dialog_update_progress_text( p_scan->p_obj, p_scan->p_dialog_id,
f_position, psz_fmt,
(double)p_cfg->i_frequency / 1000000,
i_total_services,
psz_eta );
}
return VLC_SUCCESS;
}
bool scan_IsCancelled( scan_t *p_scan )
{
if( p_scan->p_dialog_id == NULL )
return false;
return vlc_dialog_is_cancelled( p_scan->p_obj, p_scan->p_dialog_id );
}
int scan_Run( scan_t *p_scan )
{
scan_tuner_config_t cfg;
if( scan_Next( p_scan, &cfg ) )
return VLC_ENOENT;
scan_session_t *session = scan_session_New( p_scan, &cfg );
if( unlikely(session == NULL) )
return VLC_EGENERIC;
if( p_scan->pf_tune( p_scan, p_scan->p_cbdata, &cfg ) != VLC_SUCCESS )
{
scan_session_Destroy( p_scan, session );
return VLC_EGENERIC;
}
p_scan->pf_filter( p_scan, p_scan->p_cbdata, PSI_PAT_PID, true );
p_scan->pf_filter( p_scan, p_scan->p_cbdata, SI_SDT_PID, true );
if( p_scan->parameter.b_use_nit )
p_scan->pf_filter( p_scan, p_scan->p_cbdata, SI_NIT_PID, true );
/* */
uint8_t packet[TS_PACKET_SIZE * SCAN_READ_BUFFER_COUNT];
int64_t i_scan_start = vlc_tick_now();
for( ;; )
{
unsigned i_timeout = scan_session_GetTablesTimeout( session );
vlc_tick_t i_remaining = vlc_tick_now() - i_scan_start;
if( i_remaining > i_timeout )
break;
size_t i_packet_count = 0;
int i_ret = p_scan->pf_read( p_scan, p_scan->p_cbdata,
i_timeout - i_remaining,
SCAN_READ_BUFFER_COUNT,
(uint8_t *) &packet, &i_packet_count );
if( p_scan->pf_stats )
p_scan->pf_stats( p_scan, p_scan->p_cbdata, &session->i_snr );
if ( i_ret != VLC_SUCCESS )
break;
for( size_t i=0; i< i_packet_count; i++ )
{
if( scan_session_Push( session,
&packet[i * TS_PACKET_SIZE] ) )
break;
}
}
scan_session_Destroy( p_scan, session );
return VLC_SUCCESS;
}
static void scan_NotifyService( scan_t *p_scan, scan_service_t *p_service, bool b_updated )
{
if( !p_scan->pf_notify_service || !scan_service_type_Supported( p_service->type ) )
return;
p_service->stickyref = p_scan->pf_notify_service( p_scan, p_scan->p_cbdata,
p_service, p_service->stickyref,
b_updated );
}
#define scan_NotifyNewService( a, b ) scan_NotifyService( a, b, false )
#define scan_NotifyUpdatedService( a, b ) scan_NotifyService( a, b, true )
static bool GetOtherNetworkNIT( scan_session_t *p_session, uint16_t i_network_id,
dvbpsi_nit_t ***ppp_nit )
{
for( size_t i=0; i<p_session->others.i_nit; i++ )
{
if( p_session->others.pp_nit[i]->i_network_id == i_network_id )
{
*ppp_nit = &p_session->others.pp_nit[i];
return true;
}
}
return false;
}
static bool GetOtherTsSDT( scan_session_t *p_session, uint16_t i_ts_id,
dvbpsi_sdt_t ***ppp_sdt )
{
for( size_t i=0; i<p_session->others.i_sdt; i++ )
{
if( p_session->others.pp_sdt[i]->i_extension == i_ts_id )
{
*ppp_sdt = &p_session->others.pp_sdt[i];
return true;
}
}
return false;
}
static void ParsePAT( vlc_object_t *p_obj, scan_t *p_scan,
const dvbpsi_pat_t *p_pat, const scan_tuner_config_t *p_cfg,
int i_snr )
{
/* PAT must not create new service without proper config ( local ) */
if( !p_cfg )
return;
scan_multiplex_t *p_mplex = scan_FindOrCreateMultiplex( p_scan, p_pat->i_ts_id, p_cfg );
if( unlikely(p_mplex == NULL) )
return;
if( p_mplex->i_snr > 0 && i_snr > p_mplex->i_snr )
{
msg_Info( p_obj, "multiplex ts_id %" PRIu16 " freq %u snr %d replaced by freq %u snr %d",
p_mplex->i_ts_id, p_mplex->cfg.i_frequency, p_mplex->i_snr,
p_cfg->i_frequency, i_snr );
p_mplex->cfg = *p_cfg;
}
p_mplex->i_snr = i_snr;
const dvbpsi_pat_program_t *p_program;
for( p_program = p_pat->p_first_program; p_program != NULL; p_program = p_program->p_next )
{
if( p_program->i_number == 0 ) /* NIT */
continue;
scan_service_t *s = scan_multiplex_FindService( p_mplex, p_program->i_number );
if( s == NULL )
{
s = scan_service_New( p_program->i_number );
if( likely(s) )
{
if( !scan_multiplex_AddService( p_mplex, s ) ) /* OOM */
scan_service_Delete( s );
else
scan_NotifyNewService( p_scan, s );
}
}
}
}
/* FIXME handle properly string (convert to utf8) */
static void PATCallBack( scan_session_t *p_session, dvbpsi_pat_t *p_pat )
{
vlc_object_t *p_obj = p_session->p_obj;
/* */
if( p_session->local.p_pat && p_session->local.p_pat->b_current_next )
{
dvbpsi_pat_delete( p_session->local.p_pat );
p_session->local.p_pat = NULL;
}
if( p_session->local.p_pat )
{
dvbpsi_pat_delete( p_pat );
return;
}
dvbpsi_pat_program_t *p_program;
/* */
p_session->local.p_pat = p_pat;
/* */
msg_Dbg( p_obj, "new PAT ts_id=%d version=%d current_next=%d",
p_pat->i_ts_id, p_pat->i_version, p_pat->b_current_next );
for( p_program = p_pat->p_first_program; p_program != NULL; p_program = p_program->p_next )
{
msg_Dbg( p_obj, " * number=%d pid=%d", p_program->i_number, p_program->i_pid );
if( p_program->i_number == 0 )
p_session->i_nit_pid = p_program->i_pid;
}
}
static void ParseSDT( vlc_object_t *p_obj, scan_t *p_scan, const dvbpsi_sdt_t *p_sdt )
{
VLC_UNUSED(p_obj);
/* SDT must not create new service without proper config ( local )
or it must has been created by another network NIT (providing freq).
Guaranteed by parsing order( PAT, current ts SDT ) or ( NIT, SDT ) */
scan_multiplex_t *p_mplex = scan_FindMultiplex( p_scan, p_sdt->i_extension );
if( unlikely(p_mplex == NULL) )
return ;
scan_SetMultiplexScanStatus( p_scan, p_mplex, true );
if( p_mplex->i_sdt_version == UINT8_MAX )
p_mplex->i_sdt_version = p_sdt->i_version;
for( const dvbpsi_sdt_service_t *p_srv = p_sdt->p_first_service;
p_srv; p_srv = p_srv->p_next )
{
bool b_newservice = false;
scan_service_t *s = scan_multiplex_FindService( p_mplex, p_srv->i_service_id );
if( s == NULL )
{
b_newservice = true;
s = scan_service_New( p_srv->i_service_id );
if( unlikely(s == NULL) )
continue;
if( !scan_multiplex_AddService( p_mplex, s ) )
{
scan_service_Delete( s );
continue;
}
}
s->b_crypted = p_srv->b_free_ca;
for( dvbpsi_descriptor_t *p_dr = p_srv->p_first_descriptor;
p_dr; p_dr = p_dr->p_next )
{
if( p_dr->i_tag != 0x48 )
continue;
dvbpsi_service_dr_t *pD = dvbpsi_DecodeServiceDr( p_dr );
if( pD )
{
if( !s->psz_name )
s->psz_name = vlc_from_EIT( pD->i_service_name,
pD->i_service_name_length );
free( s->psz_provider );
s->psz_provider = vlc_from_EIT( pD->i_service_provider_name,
pD->i_service_provider_name_length );
s->type = pD->i_service_type;
}
}
scan_NotifyService( p_scan, s, !b_newservice );
}
}
static void SDTCallBack( scan_session_t *p_session, dvbpsi_sdt_t *p_sdt )
{
vlc_object_t *p_obj = p_session->p_obj;
dvbpsi_sdt_t **pp_stored_sdt = NULL;
if( p_sdt->i_table_id == SDT_OTHER_TS_TABLE_ID )
{
if( !GetOtherTsSDT( p_session, p_sdt->i_extension, &pp_stored_sdt ) )
{
dvbpsi_sdt_t **pp_realloc = realloc( p_session->others.pp_sdt,
(p_session->others.i_sdt + 1) * sizeof( *pp_realloc ) );
if( !pp_realloc ) /* oom */
{
dvbpsi_sdt_delete( p_sdt );
return;
}
pp_stored_sdt = &pp_realloc[p_session->others.i_sdt];
p_session->others.pp_sdt = pp_realloc;
p_session->others.i_sdt++;
}
}
else /* SDT_CURRENT_TS_TABLE_ID */
{
pp_stored_sdt = &p_session->local.p_sdt;
}
/* Store, replace, or discard */
if( *pp_stored_sdt )
{
if( (*pp_stored_sdt)->i_version == p_sdt->i_version ||
(*pp_stored_sdt)->b_current_next > p_sdt->b_current_next )
{
/* Duplicate or stored one isn't current */
dvbpsi_sdt_delete( p_sdt );
return;
}
dvbpsi_sdt_delete( *pp_stored_sdt );
}
*pp_stored_sdt = p_sdt;
/* */
msg_Dbg( p_obj, "new SDT %s ts_id=%d version=%d current_next=%d network_id=%d",
( p_sdt->i_table_id == SDT_CURRENT_TS_TABLE_ID ) ? "local" : "other",
p_sdt->i_extension, p_sdt->i_version, p_sdt->b_current_next,
p_sdt->i_network_id );
dvbpsi_sdt_service_t *p_srv;
for( p_srv = p_sdt->p_first_service; p_srv; p_srv = p_srv->p_next )
{
dvbpsi_descriptor_t *p_dr;
msg_Dbg( p_obj, " * service id=%d eit schedule=%d present=%d running=%d free_ca=%d",
p_srv->i_service_id, p_srv->b_eit_schedule,
p_srv->b_eit_present, p_srv->i_running_status,
p_srv->b_free_ca );
for( p_dr = p_srv->p_first_descriptor; p_dr; p_dr = p_dr->p_next )
{
if( p_dr->i_tag == 0x48 )
{
dvbpsi_service_dr_t *pD = dvbpsi_DecodeServiceDr( p_dr );
if( pD )
{
char str2[257];
memcpy( str2, pD->i_service_name, pD->i_service_name_length );
str2[pD->i_service_name_length] = '\0';
msg_Dbg( p_obj, " - type=%d name=%s",
pD->i_service_type, str2 );
}
}
else
{
msg_Dbg( p_obj, " * dsc 0x%x", p_dr->i_tag );
}
}
}
}
static scan_coderate_t ConvertDelDrInnerFec( uint8_t v )
{
switch(v)
{
default:
case 0x0: return SCAN_CODERATE_AUTO;
case 0x1: return SCAN_CODERATE_1_2;
case 0x2: return SCAN_CODERATE_2_3;
case 0x3: return SCAN_CODERATE_3_4;
case 0x4: return SCAN_CODERATE_5_6;
case 0x5: return SCAN_CODERATE_7_8;
case 0x6: return SCAN_CODERATE_8_9;
case 0x7: return SCAN_CODERATE_3_5;
case 0x8: return SCAN_CODERATE_4_5;
case 0x9: return SCAN_CODERATE_9_10;
case 0xF: return SCAN_CODERATE_NONE;
}
}
static scan_coderate_t ConvertDelDrCodeRate( uint8_t v )
{
if( v > 0x04 )
return SCAN_CODERATE_AUTO;
else
return ConvertDelDrInnerFec( v + 1 );
}
static void ParseNIT( vlc_object_t *p_obj, scan_t *p_scan,
const dvbpsi_nit_t *p_nit, const scan_tuner_config_t *p_cfg )
{
for( const dvbpsi_nit_ts_t *p_ts = p_nit->p_first_ts;
p_ts != NULL; p_ts = p_ts->p_next )
{
msg_Dbg( p_obj, " * ts ts_id=0x%x original_id=0x%x", p_ts->i_ts_id, p_ts->i_orig_network_id );
uint32_t i_private_data_id = 0;
dvbpsi_descriptor_t *p_dsc;
scan_tuner_config_t tscfg;
scan_tuner_config_Init( &tscfg, &p_scan->parameter );
if( p_cfg != NULL ) // p_nit->i_table_id != NIT_CURRENT_NETWORK_TABLE_ID
tscfg = *p_cfg;
dvbpsi_service_list_dr_t *p_sl = NULL;
dvbpsi_lcn_dr_t *p_lc = NULL;
dvbpsi_descriptor_t *p_nn = NULL;
for( p_dsc = p_ts->p_first_descriptor; p_dsc != NULL; p_dsc = p_dsc->p_next )
{
if( p_dsc->i_tag == 0x41 )
{
/* Store it and process it after signal config
* (required for NIT describing other networks) */
p_sl = dvbpsi_DecodeServiceListDr( p_dsc );
}
else if( p_dsc->i_tag == 0x5a )
{
dvbpsi_terr_deliv_sys_dr_t *p_t = dvbpsi_DecodeTerrDelivSysDr( p_dsc );
if( p_t )
{
tscfg.i_frequency = p_t->i_centre_frequency / 10;
tscfg.i_bandwidth = 8 - p_t->i_bandwidth;
switch(p_t->i_constellation)
{
case 0x00:
tscfg.modulation = SCAN_MODULATION_QPSK;
break;
case 0x01:
tscfg.modulation = SCAN_MODULATION_QAM_16;
break;
case 0x02:
tscfg.modulation = SCAN_MODULATION_QAM_64;
break;
default:
tscfg.modulation = SCAN_MODULATION_AUTO;
break;
}
tscfg.coderate_hp = ConvertDelDrCodeRate( p_t->i_code_rate_hp_stream );
if( p_t->i_hierarchy_information == 0x0 || p_t->i_hierarchy_information == 0x4 )
tscfg.coderate_lp = SCAN_CODERATE_NONE;
else
tscfg.coderate_lp = ConvertDelDrCodeRate( p_t->i_code_rate_lp_stream );
msg_Dbg( p_obj, " * terrestrial delivery system" );
msg_Dbg( p_obj, " * centre_frequency %u", tscfg.i_frequency );
msg_Dbg( p_obj, " * bandwidth %u", tscfg.i_bandwidth );
msg_Dbg( p_obj, " * modulation %s", scan_value_modulation( tscfg.modulation ) );
msg_Dbg( p_obj, " * hierarchy %d", p_t->i_hierarchy_information );
msg_Dbg( p_obj, " * code_rate hp %s lp %s", scan_value_coderate( tscfg.coderate_hp ),
scan_value_coderate( tscfg.coderate_hp ) );
msg_Dbg( p_obj, " * guard_interval %d", p_t->i_guard_interval );
msg_Dbg( p_obj, " * transmission_mode %d", p_t->i_transmission_mode );
msg_Dbg( p_obj, " * other_frequency_flag %d", p_t->i_other_frequency_flag );
}
}
else if( p_dsc->i_tag == 0x43 )
{
dvbpsi_sat_deliv_sys_dr_t *p_s = dvbpsi_DecodeSatDelivSysDr( p_dsc );
if( p_s )
{
tscfg.i_frequency = decode_BCD( p_s->i_frequency ) * 1000;
tscfg.i_symbolrate = decode_BCD( p_s->i_symbol_rate ) * 100;
if( unlikely(p_s->i_polarization > 0x03) )
p_s->i_polarization = 0;
const scan_polarization_t polarizations[] = {
SCAN_POLARIZATION_HORIZONTAL,
SCAN_POLARIZATION_VERTICAL,
SCAN_POLARIZATION_CIRC_LEFT,
SCAN_POLARIZATION_CIRC_RIGHT };
tscfg.polarization = polarizations[p_s->i_polarization];
switch(p_s->i_modulation_type)
{
default:
case 0x00:
tscfg.modulation = SCAN_MODULATION_AUTO;
break;
case 0x01:
tscfg.modulation = SCAN_MODULATION_QPSK;
break;
case 0x02:
tscfg.modulation = SCAN_MODULATION_PSK_8;
break;
case 0x03:
tscfg.modulation = SCAN_MODULATION_QAM_16;
break;
}
tscfg.delivery = (p_s->i_modulation_system == 0x01) ? SCAN_DELIVERY_DVB_S2
: SCAN_DELIVERY_DVB_S;
tscfg.inner_fec = ConvertDelDrInnerFec( p_s->i_fec_inner );
msg_Dbg( p_obj, " * satellite delivery system" );
msg_Dbg( p_obj, " * frequency %u", tscfg.i_frequency );
msg_Dbg( p_obj, " * symbolrate %u", tscfg.i_symbolrate );
msg_Dbg( p_obj, " * polarization %c", (char) tscfg.polarization );
msg_Dbg( p_obj, " * modulation %s", scan_value_modulation( tscfg.modulation ) );
msg_Dbg( p_obj, " * fec inner %s", scan_value_coderate( tscfg.inner_fec ) );
if( tscfg.delivery == SCAN_DELIVERY_DVB_S2 )
{
msg_Dbg( p_obj, " * system DVB-S2" );
}
}
}
else if( p_dsc->i_tag == 0x44 )
{
dvbpsi_cable_deliv_sys_dr_t *p_t = dvbpsi_DecodeCableDelivSysDr( p_dsc );
if( p_t )
{
tscfg.i_frequency = decode_BCD( p_t->i_frequency ) * 100;
tscfg.i_symbolrate = decode_BCD( p_t->i_symbol_rate ) * 100;
if( p_t->i_modulation <= 0x05 )
tscfg.modulation = p_t->i_modulation;
else
tscfg.modulation = SCAN_MODULATION_AUTO;
tscfg.inner_fec = ConvertDelDrInnerFec( p_t->i_fec_inner );
msg_Dbg( p_obj, " * Cable delivery system");
msg_Dbg( p_obj, " * frequency %d", tscfg.i_frequency );
msg_Dbg( p_obj, " * symbolrate %u", tscfg.i_symbolrate );
msg_Dbg( p_obj, " * modulation %s", scan_value_modulation( tscfg.modulation ) );
msg_Dbg( p_obj, " * fec inner %s", scan_value_coderate( tscfg.inner_fec ) );
}
}
else if( p_dsc->i_tag == 0x5f && p_dsc->i_length > 3 )
{
msg_Dbg( p_obj, " * private data specifier descriptor" );
i_private_data_id = GetDWBE( &p_dsc->p_data[0] );
msg_Dbg( p_obj, " * value 0x%8.8x", i_private_data_id );
}
else if( i_private_data_id == 0x28 && p_dsc->i_tag == 0x83 )
{
msg_Dbg( p_obj, " * logical channel descriptor (EICTA)" );
p_lc = dvbpsi_DecodeLCNDr( p_dsc );
}
else if( p_dsc->i_tag == 0x40 && p_dsc->i_length > 0 ) /* Network Name */
{
p_nn = p_dsc;
}
else
{
msg_Warn( p_obj, " * dsc 0x%x", p_dsc->i_tag );
}
}
bool b_valid = scan_tuner_config_StandardValidate( &tscfg );
if( b_valid && p_nit->i_table_id == NIT_CURRENT_NETWORK_TABLE_ID )
{
p_scan->b_multiplexes_from_nit |= b_valid;
}
scan_multiplex_t *p_mplex = scan_FindMultiplex( p_scan, p_ts->i_ts_id );
if( p_mplex == NULL && b_valid )
{
p_mplex = scan_multiplex_New( &tscfg, p_ts->i_ts_id );
if( likely(p_mplex) )
{
if ( unlikely(!scan_AddMultiplex( p_scan, p_mplex )) )
{
scan_multiplex_Delete( p_mplex );
p_mplex = NULL;
}
}
}
if( unlikely(!p_mplex) )
continue;
if( p_mplex->i_network_id == NETWORK_ID_RESERVED )
{
p_mplex->i_network_id = p_nit->i_network_id;
p_mplex->i_nit_version = p_nit->i_version;
}
/* Now process service list, and create them if tuner config is known */
if( p_sl )
{
msg_Dbg( p_obj, " * service list descriptor" );
for( uint8_t i = 0; p_sl && i < p_sl->i_service_count; i++ )
{
const uint16_t i_service_id = p_sl->i_service[i].i_service_id;
const uint8_t i_service_type = p_sl->i_service[i].i_service_type;
msg_Dbg( p_obj, " * service_id=%" PRIu16 " type=%" PRIu8,
i_service_id, i_service_type );
if( !p_cfg || p_cfg->i_frequency == 0 )
{
msg_Warn( p_obj, "cannot create service_id=%" PRIu16 " ts_id=%" PRIu16 " (no config)",
i_service_id, p_ts->i_ts_id );
continue;
}
bool b_newservice = false;
scan_service_t *s = scan_multiplex_FindService( p_mplex, i_service_id );
if( s == NULL )
{
s = scan_service_New( i_service_id );
if( unlikely(s == NULL) )
continue;
b_newservice = true;
s->type = i_service_type;
s->i_original_network_id = p_ts->i_orig_network_id;
if( !scan_multiplex_AddService( p_mplex, s ) )
{
scan_service_Delete( s );
s = NULL;
}
}
if ( s->psz_original_network_name == NULL && p_nn )
s->psz_original_network_name = strndup( (const char*) p_nn->p_data, p_nn->i_length );
scan_NotifyService( p_scan, s, !b_newservice );
}
}
/* Set virtual channel numbers */
if( p_lc )
{
for( int i = 0; i < p_lc->i_number_of_entries; i++ )
{
const uint16_t i_service_id = p_lc->p_entries[i].i_service_id;
const uint16_t i_channel_number = p_lc->p_entries[i].i_logical_channel_number;
msg_Dbg( p_obj, " * service_id=%" PRIu16 " channel_number=%" PRIu16,
i_service_id, i_channel_number );
scan_service_t *s = scan_multiplex_FindService( p_mplex, i_service_id );
if( s )
s->i_channel = i_channel_number;
}
}
}
}
static void NITCallBack( scan_session_t *p_session, dvbpsi_nit_t *p_nit )
{
vlc_object_t *p_obj = p_session->p_obj;
dvbpsi_nit_t **pp_stored_nit = NULL;
if( p_nit->i_table_id == NIT_OTHER_NETWORK_TABLE_ID )
{
if( !GetOtherNetworkNIT( p_session, p_nit->i_network_id, &pp_stored_nit ) )
{
dvbpsi_nit_t **pp_realloc = realloc( p_session->others.pp_nit,
(p_session->others.i_nit + 1) * sizeof( *pp_realloc ) );
if( !pp_realloc ) /* oom */
{
dvbpsi_nit_delete( p_nit );
return;
}
pp_stored_nit = &pp_realloc[p_session->others.i_nit];
p_session->others.pp_nit = pp_realloc;
p_session->others.i_nit++;
}
}
else /* NIT_CURRENT_NETWORK_TABLE_ID */
{
pp_stored_nit = &p_session->local.p_nit;
}
/* Store, replace, or discard */
if( *pp_stored_nit )
{
if( (*pp_stored_nit)->i_version == p_nit->i_version ||
(*pp_stored_nit)->b_current_next > p_nit->b_current_next )
{
/* Duplicate or stored one isn't current */
dvbpsi_nit_delete( p_nit );
return;
}
dvbpsi_nit_delete( *pp_stored_nit );
}
*pp_stored_nit = p_nit;
msg_Dbg( p_obj, "new NIT %s network_id=%d version=%d current_next=%d",
( p_nit->i_table_id == NIT_CURRENT_NETWORK_TABLE_ID ) ? "local" : "other",
p_nit->i_network_id, p_nit->i_version, p_nit->b_current_next );
/* */
dvbpsi_descriptor_t *p_dsc;
for( p_dsc = p_nit->p_first_descriptor; p_dsc != NULL; p_dsc = p_dsc->p_next )
{
if( p_dsc->i_tag == 0x40 && p_dsc->i_length > 0 )
{
msg_Dbg( p_obj, " * network name descriptor" );
char str1[257];
memcpy( str1, p_dsc->p_data, p_dsc->i_length );
str1[p_dsc->i_length] = '\0';
msg_Dbg( p_obj, " * name %s", str1 );
}
#if 0
else if( p_dsc->i_tag == 0x4a )
{
dvbpsi_linkage_dr_t *p_l = dvbpsi_DecodeLinkageDr( p_dsc );
if( p_l )
{
msg_Dbg( p_obj, " * linkage descriptor" );
msg_Dbg( p_obj, " * ts_id %" PRIu16, p_l->i_transport_stream_id );
msg_Dbg( p_obj, " * on_id %" PRIu16, p_l->i_original_network_id );
msg_Dbg( p_obj, " * service_id %" PRIu16, p_l->i_service_id );
msg_Dbg( p_obj, " * linkage_type %" PRIu8, p_l->i_linkage_type );
}
}
#endif
else
{
msg_Dbg( p_obj, " * dsc 0x%x", p_dsc->i_tag );
}
}
}
static void PSINewTableCallBack( dvbpsi_t *h, uint8_t i_table_id, uint16_t i_extension, void *p_data )
{
scan_session_t *p_session = (scan_session_t *)p_data;
if( i_table_id == SDT_CURRENT_TS_TABLE_ID || i_table_id == SDT_OTHER_TS_TABLE_ID )
{
if( !dvbpsi_sdt_attach( h, i_table_id, i_extension, (dvbpsi_sdt_callback)SDTCallBack, p_session ) )
msg_Err( p_session->p_obj, "PSINewTableCallback: failed attaching SDTCallback" );
}
else if( i_table_id == NIT_CURRENT_NETWORK_TABLE_ID || i_table_id == NIT_OTHER_NETWORK_TABLE_ID )
{
if( !dvbpsi_nit_attach( h, i_table_id, i_extension, (dvbpsi_nit_callback)NITCallBack, p_session ) )
msg_Err( p_session->p_obj, "PSINewTableCallback: failed attaching NITCallback" );
}
}
static scan_session_t *scan_session_New( scan_t *p_scan, const scan_tuner_config_t *p_cfg )
{
scan_session_t *p_session = malloc( sizeof( *p_session ) );
if( unlikely(p_session == NULL) )
return NULL;
p_session->p_obj = p_scan->p_obj;
p_session->cfg = *p_cfg;
p_session->i_snr = -1;
p_session->local.p_pat = NULL;
p_session->local.p_sdt = NULL;
p_session->local.p_nit = NULL;
p_session->i_nit_pid = -1;
p_session->b_use_nit = p_scan->parameter.b_use_nit;
p_session->type = p_scan->parameter.type;
p_session->others.i_nit = 0;
p_session->others.i_sdt = 0;
p_session->others.pp_nit = NULL;
p_session->others.pp_sdt = NULL;
p_session->p_pathandle = NULL;
p_session->p_sdthandle = NULL;
p_session->p_nithandle = NULL;
return p_session;
}
static void scan_session_Delete( scan_session_t *p_session )
{
for( size_t i=0; i< p_session->others.i_sdt; i++ )
dvbpsi_sdt_delete( p_session->others.pp_sdt[i] );
free( p_session->others.pp_sdt );
for( size_t i=0; i< p_session->others.i_nit; i++ )
dvbpsi_nit_delete( p_session->others.pp_nit[i] );
free( p_session->others.pp_nit );
if( p_session->p_pathandle )
{
dvbpsi_pat_detach( p_session->p_pathandle );
if( p_session->local.p_pat )
dvbpsi_pat_delete( p_session->local.p_pat );
}
if( p_session->p_sdthandle )
{
dvbpsi_DetachDemux( p_session->p_sdthandle );
if( p_session->local.p_sdt )
dvbpsi_sdt_delete( p_session->local.p_sdt );
}
if( p_session->p_nithandle )
{
dvbpsi_DetachDemux( p_session->p_nithandle );
if( p_session->local.p_nit )
dvbpsi_nit_delete( p_session->local.p_nit );
}
free( p_session );
}
static void scan_session_Destroy( scan_t *p_scan, scan_session_t *p_session )
{
dvbpsi_pat_t *p_pat = p_session->local.p_pat;
dvbpsi_sdt_t *p_sdt = p_session->local.p_sdt;
dvbpsi_nit_t *p_nit = p_session->local.p_nit;
/* Parse PAT (Declares only local services/programs) */
if( p_pat )
ParsePAT( p_scan->p_obj, p_scan, p_pat, &p_session->cfg, p_session->i_snr );
/* Parse NIT (Declares local services/programs) */
if( p_nit )
ParseNIT( p_scan->p_obj, p_scan, p_nit, &p_session->cfg );
/* Parse SDT (Maps names to programs) */
if( p_sdt )
ParseSDT( p_scan->p_obj, p_scan, p_sdt );
/* Do the same for all other networks */
for( size_t i=0; i<p_session->others.i_nit; i++ )
ParseNIT( p_scan->p_obj, p_scan, p_session->others.pp_nit[i], NULL );
/* Map service name for all other ts/networks */
for( size_t i=0; i<p_session->others.i_sdt; i++ )
ParseSDT( p_scan->p_obj, p_scan, p_session->others.pp_sdt[i] );
/* */
scan_session_Delete( p_session );
}
static int ScanServiceCmp( const void *a, const void *b )
{
const scan_service_t *sa = *((const scan_service_t**)a);
const scan_service_t *sb = *((const scan_service_t**)b);
if( sa->i_channel == sb->i_channel )
{
if( sa->psz_name && sb->psz_name )
return strcmp( sa->psz_name, sb->psz_name );
return 0;
}
if( sa->i_channel < sb->i_channel )
return -1;
else if( sa->i_channel > sb->i_channel )
return 1;
return 0;
}
static block_t *BlockString( const char *psz )
{
block_t *p = block_Alloc( strlen(psz) );
if( p )
memcpy( p->p_buffer, psz, p->i_buffer );
return p;
}
void scan_set_NotifyCB( scan_t *p_scan, scan_service_notify_cb pf )
{
p_scan->pf_notify_service = pf;
}
const char * scan_service_GetName( const scan_service_t *s )
{
return s->psz_name;
}
const char * scan_service_GetProvider( const scan_service_t *s )
{
return s->psz_provider;
}
uint16_t scan_service_GetProgram( const scan_service_t *s )
{
return s->i_program;
}
const char * scan_service_GetNetworkName( const scan_service_t *s )
{
if( s->p_mplex )
return s->p_mplex->psz_network_name;
else
return NULL;
}
char * scan_service_GetUri( const scan_service_t *s )
{
char *psz_mrl = NULL;
int i_ret = -1;
switch( s->p_mplex->cfg.type )
{
case SCAN_DVB_T:
i_ret = asprintf( &psz_mrl, "dvb://frequency=%d:bandwidth=%d:modulation=%s",
s->p_mplex->cfg.i_frequency,
s->p_mplex->cfg.i_bandwidth,
scan_value_modulation( s->p_mplex->cfg.modulation ) );
break;
case SCAN_DVB_S:
i_ret = asprintf( &psz_mrl, "dvb://frequency=%d:srate=%d:polarization=%c:fec=%s",
s->p_mplex->cfg.i_frequency,
s->p_mplex->cfg.i_symbolrate,
(char) s->p_mplex->cfg.polarization,
scan_value_coderate( s->p_mplex->cfg.inner_fec ) );
break;
case SCAN_DVB_C:
i_ret = asprintf( &psz_mrl, "dvb://frequency=%d:srate=%d:modulation=%s:fec=%s",
s->p_mplex->cfg.i_frequency,
s->p_mplex->cfg.i_symbolrate,
scan_value_modulation( s->p_mplex->cfg.modulation ),
scan_value_coderate( s->p_mplex->cfg.inner_fec ) );
default:
break;
}
return (i_ret >=0) ? psz_mrl : NULL;
}
block_t *scan_GetM3U( scan_t *p_scan )
{
vlc_object_t *p_obj = p_scan->p_obj;
block_t *p_playlist = BlockString( "#EXTM3U\n\n" );
if( !p_playlist )
return NULL;
const size_t i_total_services = scan_CountServices( p_scan );
size_t i_filtered_count = 0;
const scan_service_t **pp_filtered_list = vlc_alloc( i_total_services, sizeof(scan_service_t *) );
if( !pp_filtered_list )
{
block_Release( p_playlist );
return NULL;
}
for( size_t j = 0; j < p_scan->i_multiplex; j++ )
{
const scan_multiplex_t *p_mplex = p_scan->pp_multiplex[j];
for( size_t i = 0; i < p_mplex->i_services; i++ )
{
const scan_service_t *s = p_mplex->pp_services[i];
if( !scan_service_type_Supported( s->type ) )
{
/* We should only select service that have been described by SDT */
msg_Dbg( p_obj, "scan_GetM3U: ignoring service number %d", s->i_program );
continue;
}
pp_filtered_list[i_filtered_count++] = s;
}
}
/* */
qsort( pp_filtered_list, i_filtered_count, sizeof(scan_service_t *), ScanServiceCmp );
for( size_t i = 0; i < i_filtered_count; i++ )
{
const scan_service_t *s = pp_filtered_list[i];
const char *psz_type;
switch( s->type )
{
case SERVICE_TYPE_DIGITAL_TELEVISION: psz_type = "Digital television"; break;
case SERVICE_TYPE_DIGITAL_TELEVISION_AC_SD: psz_type = "Digital television advanced codec SD"; break;
case SERVICE_TYPE_DIGITAL_TELEVISION_AC_HD: psz_type = "Digital television advanced codec HD"; break;
case SERVICE_TYPE_DIGITAL_RADIO: psz_type = "Digital radio"; break;
default:
psz_type = "Unknown";
break;
}
msg_Warn( p_obj, "scan_GetM3U: service number %d type '%s' name '%s' channel %d cypted=%d|"
"network_id %d (nit:%d sdt:%d)| f=%d bw=%d snr=%d modulation=%s",
s->i_program, psz_type, s->psz_name, s->i_channel, s->b_crypted,
s->p_mplex->i_network_id, s->p_mplex->i_nit_version, s->p_mplex->i_sdt_version,
s->p_mplex->cfg.i_frequency, s->p_mplex->cfg.i_bandwidth, s->p_mplex->i_snr,
scan_value_modulation( s->p_mplex->cfg.modulation ) );
char *psz_mrl = scan_service_GetUri( s );
if( psz_mrl == NULL )
continue;
char *psz;
int i_ret = asprintf( &psz, "#EXTINF:,,%s\n"
"#EXTVLCOPT:program=%d\n"
"%s\n\n",
s->psz_name && * s->psz_name ? s->psz_name : "Unknown",
s->i_program,
psz_mrl );
free( psz_mrl );
if( i_ret != -1 )
{
block_t *p_block = BlockString( psz );
if( p_block )
block_ChainAppend( &p_playlist, p_block );
free( psz );
}
}
free( pp_filtered_list );
return p_playlist ? block_ChainGather( p_playlist ) : NULL;
}
#define dvbpsi_packet_push(a,b) dvbpsi_packet_push(a, (uint8_t *)b)
static bool scan_session_Push( scan_session_t *p_scan, const uint8_t *p_packet )
{
if( p_packet[0] != 0x47 )
return false;
/* */
const int i_pid = ( (p_packet[1]&0x1f)<<8) | p_packet[2];
if( i_pid == PSI_PAT_PID )
{
if( !p_scan->p_pathandle )
{
p_scan->p_pathandle = dvbpsi_new( &dvbpsi_messages, DVBPSI_MSG_DEBUG );
if( !p_scan->p_pathandle )
return false;
p_scan->p_pathandle->p_sys = (void *) p_scan->p_obj;
if( !dvbpsi_pat_attach( p_scan->p_pathandle, (dvbpsi_pat_callback)PATCallBack, p_scan ) )
{
dvbpsi_delete( p_scan->p_pathandle );
p_scan->p_pathandle = NULL;
return false;
}
}
if( p_scan->p_pathandle )
dvbpsi_packet_push( p_scan->p_pathandle, p_packet );
}
else if( i_pid == SI_SDT_PID )
{
if( !p_scan->p_sdthandle )
{
p_scan->p_sdthandle = dvbpsi_new( &dvbpsi_messages, DVBPSI_MSG_DEBUG );
if( !p_scan->p_sdthandle )
return false;
p_scan->p_sdthandle->p_sys = (void *) p_scan->p_obj;
if( !dvbpsi_AttachDemux( p_scan->p_sdthandle, (dvbpsi_demux_new_cb_t)PSINewTableCallBack, p_scan ) )
{
dvbpsi_delete( p_scan->p_sdthandle );
p_scan->p_sdthandle = NULL;
return false;
}
}
if( p_scan->p_sdthandle )
dvbpsi_packet_push( p_scan->p_sdthandle, p_packet );
}
else if( p_scan->b_use_nit && i_pid == SI_NIT_PID ) /*if( i_pid == p_scan->i_nit_pid )*/
{
if( !p_scan->p_nithandle )
{
p_scan->p_nithandle = dvbpsi_new( &dvbpsi_messages, DVBPSI_MSG_DEBUG );
if( !p_scan->p_nithandle )
return false;
p_scan->p_nithandle->p_sys = (void *) p_scan->p_obj;
if( !dvbpsi_AttachDemux( p_scan->p_nithandle, (dvbpsi_demux_new_cb_t)PSINewTableCallBack, p_scan ) )
{
dvbpsi_delete( p_scan->p_nithandle );
p_scan->p_nithandle = NULL;
return false;
}
}
if( p_scan->p_nithandle )
dvbpsi_packet_push( p_scan->p_nithandle, p_packet );
}
return p_scan->local.p_pat && p_scan->local.p_sdt &&
(!p_scan->b_use_nit || p_scan->local.p_nit);
}
static unsigned scan_session_GetTablesTimeout( const scan_session_t *p_session )
{
unsigned i_time = 0;
if( !p_session->local.p_pat )
{
i_time = 500;
}
else if( !p_session->local.p_sdt )
{
i_time = 2*1000;
}
else if( !p_session->local.p_nit && p_session->b_use_nit )
{
if( p_session->type == SCAN_DVB_T )
i_time = 6000;
else
i_time = 5000;
}
return i_time * 2 * 1000;
}
const char *scan_value_modulation( scan_modulation_t m )
{
switch(m)
{
case SCAN_MODULATION_QAM_16: return "16QAM";
case SCAN_MODULATION_QAM_32: return "32QAM";
case SCAN_MODULATION_QAM_64: return "64QAM";
case SCAN_MODULATION_QAM_128: return "128QAM";
case SCAN_MODULATION_QAM_256: return "256QAM";
case SCAN_MODULATION_QAM_AUTO: return "QAM";
case SCAN_MODULATION_PSK_8: return "8PSK";
case SCAN_MODULATION_QPSK: return "QPSK";
case SCAN_MODULATION_DQPSK: return "DQPSK";
case SCAN_MODULATION_APSK_16: return "16APSK";
case SCAN_MODULATION_APSK_32: return "32APSK";
case SCAN_MODULATION_VSB_8: return "8VSB";
case SCAN_MODULATION_VSB_16: return "16VSB";
case SCAN_MODULATION_QAM_4NR:
case SCAN_MODULATION_AUTO:
default: return "";
}
}
const char *scan_value_coderate( scan_coderate_t c )
{
switch( c )
{
case SCAN_CODERATE_NONE: return "0";
case SCAN_CODERATE_1_2: return "1/2";
case SCAN_CODERATE_2_3: return "2/3";
case SCAN_CODERATE_3_4: return "3/4";
case SCAN_CODERATE_3_5: return "3/5";
case SCAN_CODERATE_4_5: return "4/5";
case SCAN_CODERATE_5_6: return "5/6";
case SCAN_CODERATE_7_8: return "7/8";
case SCAN_CODERATE_8_9: return "8/9";
case SCAN_CODERATE_9_10: return "9/10";
case SCAN_CODERATE_AUTO:
default: return "";
}
}