blob: aa47f90463a112e72cd78e442b2251aafcb774e4 [file] [log] [blame]
// Copyright 2011 Google Inc. All Rights Reserved.
// Author: qianzhang@google.com (Qian Zhang)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <inttypes.h>
#include "utility.h"
#include "channel_buffer.h"
#include "libhdhomerun/hdhomerun.h"
#include "hdhomerun_tuner.h"
#include "hdhomerun_dev.h"
#include "sagelog.h"
#define PARSE_TIMEOUT 2
#define CHANNE_SCAN_SRC "227.0.0.1"
#define CHANNEL_SCAN_PORT 6069
static volatile sig_atomic_t sigabort_flag = FALSE;
static volatile sig_atomic_t siginfo_flag = FALSE;
static const char* map_tbl[] ={ "us-bcast", "us-cable", "us-hrc", "us-irc", "au-bcast", "tw-bcast", "eu-bcast", 0x0 };
static void sigabort_handler(int arg)
{
sigabort_flag = TRUE;
}
static void siginfo_handler(int arg)
{
siginfo_flag = TRUE;
}
static void register_signal_handlers(sig_t sigpipe_handler, sig_t sigint_handler, sig_t siginfo_handler)
{
#if defined(SIGPIPE)
signal(SIGPIPE, sigpipe_handler);
#endif
#if defined(SIGINT)
signal(SIGINT, sigint_handler);
#endif
#if defined(SIGINFO)
signal(SIGINFO, siginfo_handler);
#endif
}
//channel format: map:us-cable,auto:68
void* open_hdhr( char* name )
{
struct hdhomerun_device_t *hd;
hd = hdhomerun_device_create_from_str( name, NULL);
return hd;
}
void close_hdhr( void* device )
{
struct hdhomerun_device_t *hd =(struct hdhomerun_device_t *)device;
if ( hd ) {
hdhomerun_device_stream_stop( hd );
hdhomerun_device_destroy( hd );
}
}
int tune_channel( void* device, char *channel, char *program, char* stream_target )
{
char channel_str[32]="auto:";
char vchannel_str[16];
char channel_map[32];
struct hdhomerun_device_t *hd =(struct hdhomerun_device_t *)device;
struct hdhomerun_channel_list_t *channel_list=NULL;
int ret;
if ( hd == NULL ) {
printf( "hdhomerun invalid handle\n");
return -1;
}
if ( !get_string_by_name( channel, "vchan", vchannel_str, sizeof(vchannel_str), NULL ) ) {
vchannel_str[0] = 0x0;
if ( !get_string_by_name( channel, "auto", channel_str+5, sizeof(channel_str)-5, NULL ) ) {
printf("hdhomerun %p invalid channel %s\n", device, channel );
return -1;
}
if ( !get_string_by_name( channel, "map", channel_map, sizeof(channel_map), NULL ) ) {
printf("hdhomerun %p invalid channel map %s\n", device, channel );
strncpy( channel_map, "us-cable", sizeof(channel_map) );
}
if ( program[0] == 0x0 ) {
printf("hdhomerun %p invalid channel program %s\n", device, program );
return -1;
}
channel_list = hdhomerun_channel_list_create( channel_map );
if ( channel_list == NULL ) {
printf( "hdhomerun failed to create channel map\n");
return -1;
}
ret = hdhomerun_device_set_tuner_channelmap( hd, channel_map );
if ( ret < 0 )
printf( "hdhomerun failed to set tuner channel map %s\n", channel_map );
ret = hdhomerun_device_set_tuner_channel( hd, channel_str );
if ( ret < 0 )
printf("hdhomerun tuning failed:%s ret:%d\n", channel_str, ret );
ret = hdhomerun_device_set_tuner_program( hd, program );
if ( ret <= 0 )
printf( "hdhomerun failed set program:%s ret:%d\n", program, ret );
} else {
ret = hdhomerun_device_set_tuner_vchannel( hd, vchannel_str );
if ( ret < 0 )
printf("hdhomerun tuning failed: vchannel:%s ret:%d\n", vchannel_str, ret );
}
char target[64];
snprintf( target, sizeof(target), "%s ttl-16",stream_target );
ret = hdhomerun_device_set_tuner_target( hd, target );
if ( ret < 0 ) {
printf( "hdhomerun failed set target %s ret:%d\n", target, ret );
}
if ( channel_list )
hdhomerun_channel_list_destroy( channel_list );
return ret;
}
//channel format: map:us-cable,auto:68
int stop_channel( void* device )
{
int ret;
struct hdhomerun_device_t *hd =(struct hdhomerun_device_t *)device;
if ( hd == NULL ) {
printf( "hdhomerun invalid handle\n");
return -1;
}
ret = hdhomerun_device_set_tuner_channel( hd, "none" );
if ( ret < 0 ) {
printf( "hdhomerun failed set channel none to stop streaming ret:%d\n", ret );
}
ret = hdhomerun_device_set_tuner_vchannel( hd, "none" );
if ( ret < 0 ) {
printf( "hdhomerun failed set vchannel none to stop streaming ret:%d\n", ret );
}
ret = hdhomerun_device_set_tuner_target( hd, "none" );
if ( ret < 0 ) {
printf( "hdhomerun failed set target none to stop streaming ret:%d\n", ret );
}
return ret;
}
char* get_unit_name( struct hdhr_tuner_t *ht, char* name, int size )
{
char* p = strchr( ht->name, '-' );
if ( p != NULL ) {
int len = p - ht->name;
strncpy( name, ht->name, len );
name[len] = 0x0;
return name;
}
name[ 0 ] = 0x0;
return name;
}
static char* ip4_address( int ip4, char *buf, int size )
{
snprintf( buf, size, "%u.%u.%u.%u",
(unsigned int)(ip4 >> 24) & 0xFF, (unsigned int)(ip4 >> 16) & 0xFF,
(unsigned int)(ip4 >> 8) & 0xFF, (unsigned int)(ip4 >> 0) & 0xFF );
return buf;
}
int discover_device( struct hdhr_tuner_t *ht, int max_ht_num )
{
int i, k, num = 0;
struct hdhomerun_discover_device_t *device_info = malloc( max_ht_num * sizeof(struct hdhomerun_discover_device_t) );
int ret = hdhomerun_discover_find_devices_custom(0, HDHOMERUN_DEVICE_TYPE_TUNER, -1, device_info, 16);
if ( ret <= 0 ) {
free( device_info );
return 0;
}
//printf( "found %d hdhomerun device\n", ret );
for ( i = 0; i<ret; i++ ) {
//printf( "ip:%x type:%x id:%x num:%d\n", device_info[i].ip_addr, device_info[i].device_type, device_info[i].device_id,
// device_info[i].tuner_count );
for ( k = 0; k<device_info[i].tuner_count && num < max_ht_num; k++ ) {
snprintf( ht[num].name, sizeof(ht[num].name), "%X-%d", device_info[i].device_id, k );
ip4_address( device_info[i].ip_addr, ht[num].ip, sizeof(ht[num].ip) );
ht[num].type = device_info[i].device_type;
ht[num].cc_tuner = 0;
ht[num].model[0] = 0;
num++;
}
}
free( device_info );
return num;
}
static char *get_tune_string( char* result, char* tune_str, int tune_str_len )
{
int i = 0;
int ch;
while ( map_tbl[i][0] ) {
if ( get_int_val_by_name( result, map_tbl[i], &ch, NULL ) > 0 ) {
snprintf( tune_str, tune_str_len, "map:%s|auto:%d", map_tbl[i], ch );
return tune_str;
}
i++;
}
return "";
}
static int get_tune_channel( char* result )
{
int i = 0;
int ch;
while ( map_tbl[i][0] ) {
if ( get_int_val_by_name( result, map_tbl[i], &ch, NULL ) > 0 ) {
return ch;
}
i++;
}
return 0;
}
int scan_all( void* device, struct channel_entry_t *ce, int ce_num )
{
int found_channel = 0;
struct hdhomerun_device_t *hd =(struct hdhomerun_device_t *)device;
if ( hd == NULL ) {
printf( "hdhomerun invalid handle\n");
return -1;
}
char *ret_error;
if (hdhomerun_device_tuner_lockkey_request(hd, &ret_error) <= 0) {
fprintf(stderr, "failed to lock tuner\n");
if (ret_error) {
fprintf(stderr, "%s\n", ret_error);
}
return -1;
}
hdhomerun_device_set_tuner_target(hd, "none");
char *channelmap;
if (hdhomerun_device_get_tuner_channelmap(hd, &channelmap) <= 0) {
fprintf(stderr, "failed to query channelmap from device\n");
return -1;
}
const char *channelmap_scan_group = hdhomerun_channelmap_get_channelmap_scan_group(channelmap);
if (!channelmap_scan_group) {
fprintf(stderr, "unknown channelmap '%s'\n", channelmap);
return -1;
}
if (hdhomerun_device_channelscan_init(hd, channelmap_scan_group) <= 0) {
fprintf(stderr, "failed to initialize channel scan\n");
return -1;
}
register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler);
int ret = 0;
while (!sigabort_flag && found_channel < ce_num ) {
struct hdhomerun_channelscan_result_t result;
char tune_str[32];
char channel_name[32];
int channel_num;
ret = hdhomerun_device_channelscan_advance( hd, &result );
if (ret <= 0) {
break;
}
ret = hdhomerun_device_channelscan_detect( hd, &result );
if (ret < 0) {
break;
}
if (ret == 0) {
continue;
}
if ( 0 )
printf("LOCK: %s %s (ss=%u snq=%u seq=%u)\n", get_tune_string( result.channel_str, tune_str, sizeof(tune_str)),
result.status.lock_str, result.status.signal_strength,
result.status.signal_to_noise_quality, result.status.symbol_error_quality
);
channel_num = get_tune_channel( result.channel_str );
int i;
for (i = 0; i < result.program_count; i++) {
struct hdhomerun_channelscan_program_t *program = &result.programs[i];
if ( strstr( program->program_str, "encrypted" ) == NULL ) {
int program_id = atoi( program->program_str );
if ( !get_string_by_token( program->program_str, 2, channel_name, sizeof(channel_name), NULL ) ) {
if ( !get_string_by_token( program->program_str, 1, channel_name, sizeof(channel_name), NULL ) )
snprintf( channel_name, sizeof(channel_name), "%d.%d", channel_num, i );
else {
if ( !strcmp(channel_name, "0" ) )
snprintf( channel_name, sizeof(channel_name), "%d.%d", channel_num, i );
}
}
snprintf( ce->dev_tuning, sizeof(ce->dev_tuning), "%s prog:%d", get_tune_string( result.channel_str, tune_str, sizeof(tune_str) ),
program_id );
strncpy( ce->ch_name, channel_name, sizeof(ce->ch_name) );
strncpy( ce->dev_name, device, sizeof(ce->dev_name) );
ce->ch = found_channel;
ce->frq = result.frequency;
ce->av_inf[0] = 0x0;
ce++;
if ( ++found_channel >= ce_num )
break;
printf("o");
}
}
printf( "." ); fflush( stdout );
}
hdhomerun_device_tuner_lockkey_release(hd);
if (ret < 0) {
fprintf(stderr, "communication error sending request to hdhomerun device\n");
}
return found_channel;
}
struct vchan_tbl_t* create_vchan_tbl( struct hdhr_tuner_t *ht )
{
struct vchan_tbl_t *vt = malloc( sizeof( struct vchan_tbl_t ));
vt->ht = *ht;
vt->channel_size = 500;
vt->channel_num = 0;
vt->vchan = malloc( sizeof(struct vchan_t )*vt->channel_size );
memset( vt->vchan, 0x0, sizeof(struct vchan_t )*vt->channel_size );
return vt;
}
void release_vchan_tbl( struct vchan_tbl_t *vt )
{
if ( vt->vchan );
free( vt->vchan );
free( vt );
}
int grow_vhcan_tbl( struct vchan_tbl_t *vt )
{
int new_channel_size = vt->channel_size + 100;
struct vchan_t * new_vchan_list = malloc( sizeof(struct vchan_t)*new_channel_size );
if ( new_vchan_list == NULL )
return 0;
memcpy( new_vchan_list, vt->vchan, vt->channel_size*sizeof(struct vchan_t) );
free( vt->vchan );
vt->vchan = new_vchan_list;
vt->channel_size = new_channel_size;
return 1;
}
int tuner_input_sharing( struct hdhr_tuner_t *ht1, struct hdhr_tuner_t *ht2 )
{
if ( ht1->cc_tuner == 1 && !strcmp( ht1->ip, ht2->ip ) )
return 1;
return 0;
}
#ifdef TEST_APP
static int scan_channel( void* device, int index, struct channel_entry_t *ce, int ce_channel_num, int ce_num )
{
int found_channel = 0;
struct hdhomerun_device_t *hd =(struct hdhomerun_device_t *)device;
if ( hd == NULL ) {
printf( "hdhomerun invalid handle\n");
return -1;
}
char *ret_error;
if (hdhomerun_device_tuner_lockkey_request(hd, &ret_error) <= 0) {
fprintf(stderr, "failed to lock tuner\n");
if (ret_error) {
fprintf(stderr, "%s\n", ret_error);
}
return -1;
}
hdhomerun_device_set_tuner_target(hd, "none");
char *channelmap;
if (hdhomerun_device_get_tuner_channelmap(hd, &channelmap) <= 0) {
fprintf(stderr, "failed to query channelmap from device\n");
return -1;
}
const char *channelmap_scan_group = hdhomerun_channelmap_get_channelmap_scan_group(channelmap);
if (!channelmap_scan_group) {
fprintf(stderr, "unknown channelmap '%s'\n", channelmap);
return -1;
}
if (hdhomerun_device_channelscan_init(hd, channelmap_scan_group) <= 0) {
fprintf(stderr, "failed to initialize channel scan\n");
return -1;
}
register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler);
int ret = 0, i;
do {
struct hdhomerun_channelscan_result_t result;
char tune_str[32];
char channel_name[32];
int channel_num;
ret = hdhomerun_device_channelscan_at( hd, index, &result );
//ret = hdhomerun_device_channelscan_advance( hd, &result );
if (ret <= 0) {
break;
}
/* skip dumplicated channel*/
for ( i = 0; i<ce_channel_num; i++ ) {
if ( ce->frq == result.frequency )
break;
}
if ( ce->frq == result.frequency )
break;
ret = hdhomerun_device_channelscan_detect( hd, &result );
if (ret < 0) {
break;
}
if (ret == 0) {
continue;
}
if ( 0 )
printf("LOCK: %s %s (ss=%u snq=%u seq=%u)\n", get_tune_string( result.channel_str, tune_str, sizeof(tune_str)),
result.status.lock_str, result.status.signal_strength,
result.status.signal_to_noise_quality, result.status.symbol_error_quality
);
channel_num = get_tune_channel( result.channel_str );
for ( i = 0; i < result.program_count; i++) {
struct hdhomerun_channelscan_program_t *program = &result.programs[i];
if ( strstr( program->program_str, "encrypted" ) == NULL ) {
int program_id = atoi( program->program_str );
if ( !get_string_by_token( program->program_str, 2, channel_name, sizeof(channel_name), NULL ) ) {
if ( !get_string_by_token( program->program_str, 1, channel_name, sizeof(channel_name), NULL ) )
snprintf( channel_name, sizeof(channel_name), "%d.%d", channel_num, i );
else {
if ( !strcmp(channel_name, "0" ) )
snprintf( channel_name, sizeof(channel_name), "%d.%d", channel_num, i );
}
}
struct channel_entry_t *ce_p = &ce[ce_channel_num];
snprintf( ce->dev_tuning, sizeof(ce->dev_tuning), "%s prog:%d", get_tune_string( result.channel_str, tune_str, sizeof(tune_str) ),
program_id );
strncpy( ce_p->ch_name, channel_name, sizeof(ce_p->ch_name) );
strncpy( ce_p->dev_name, device, sizeof(ce_p->dev_name) );
ce_p->ch = found_channel+ce_channel_num;
ce_p->frq = result.frequency;
ce_p->av_inf[0] = 0x0;
char stream_tar[32];
snprintf( stream_tar, sizeof(stream_tar), "udp://%s:%u", CHANNE_SCAN_SRC, CHANNEL_SCAN_PORT );
ret = tune_channel( hd, ce_p->dev_tuning, "", stream_tar );
if ( ret > 0 )
ret = parse_avinf( CHANNE_SCAN_SRC, CHANNEL_SCAN_PORT, ce_p->av_inf, sizeof(ce_p->av_inf), PARSE_TIMEOUT );
if ( ret > 0 )
ret = strstr( ce_p->av_inf, "ENCRYPTED" )==NULL && strstr( ce_p->av_inf, "NO-DATA" )==NULL ;
if ( ret )
if ( ++ce_channel_num > ce_num )
break;
}
}
} while ( 0 );
hdhomerun_device_tuner_lockkey_release(hd);
if (ret < 0) {
fprintf(stderr, "communication error sending request to hdhomerun device\n");
}
return found_channel;
}
int scan_vchannel( void* device, int vchannel, struct channel_entry_t *ce )
{
int ret;
struct hdhomerun_device_t *hd =(struct hdhomerun_device_t *)device;
if ( hd == NULL ) {
sage_log(( _LOG_ERROR, 3, "hdhomerun invalid handle." ));
return -1;
}
ce->state = 1;
ce->ch = vchannel;
ce->frq = 0;
ce->av_inf[0] = 0x0;
char stream_tar[32];
snprintf( ce->ch_name, sizeof(ce->ch_name), "%d", vchannel );
snprintf( ce->dev_tuning, sizeof(ce->dev_tuning), "vchan:%d", vchannel );
snprintf( stream_tar, sizeof(stream_tar), "udp://%s:%u", CHANNE_SCAN_SRC, CHANNEL_SCAN_PORT );
ret = tune_channel( device, ce->dev_tuning, "", stream_tar );
if ( ret > 0 )
ret = parse_avinf( CHANNE_SCAN_SRC, CHANNEL_SCAN_PORT, ce->av_inf, sizeof(ce->av_inf), PARSE_TIMEOUT );
if ( ret > 0 )
ret = strstr( ce->av_inf, "ENCRYPTED" )==NULL && strstr( ce->av_inf, "NO-DATA" )==NULL ;
if ( ret <= 0 ) {
ce->state = 0;
return 0;
}
return 1;
}
static int _make_channel_entry_buffer( struct channel_entry_t *ce, char *buf, int size )
{
int pos = 0;
pos += snprintf( buf+pos, size-pos, "ch:%03d ", ce->ch );
if ( ce->ch_name[0] )
pos += snprintf( buf+pos, size-pos, "name:%s ", ce->ch_name );
if ( ce->src_ip[0] && ce->src_port )
pos += snprintf( buf+pos, size-pos, "ip:%s port:%d ", ce->src_ip, ce->src_port);
if ( ce->epg_id[0] )
pos += snprintf( buf+pos, size-pos, "epg:%s ", ce->epg_id );
if ( ce->dev_name[0] )
pos += snprintf( buf+pos, size-pos, "dev:%s ", ce->dev_name );
if ( ce->dev_tuning[0] )
pos += snprintf( buf+pos, size-pos, "tune:%s ", ce->dev_tuning );
if ( ce->av_inf[0] )
pos += snprintf( buf+pos, size-pos, "av:\"%s\" ", ce->av_inf );
pos += snprintf( buf+pos, size-pos, "\n" );
return pos;
}
static int _save_channel_entry( char* file_name, struct channel_entry_t *ce, int num )
{
int i;
char buf[2048];
FILE *fp;
fp = fopen( file_name, "w" );
if ( fp == NULL ) {
//printf( "Can't open file %s to save channel table, errno:%d\n", file_name, errno );
return -1;
}
fputs( "#Google sagetv channel table ver 1.0., maxium 2048 bytes per line\n", fp );
fprintf( fp, "total_channel:%d\n", num );
for ( i = 0; i<num; i++ ) {
_make_channel_entry_buffer( ce, buf, sizeof(buf) );
fputs( buf, fp );
ce++;
}
fclose( fp );
return 1;
}
int main( int argc, char* argv[] )
{
int i, ret = 0 ;
int tuner_count = 0;
struct hdhr_tuner_t ht[16];
ret = discover_device( ht, 16 );
printf( "found tuners:%d\n", ret );
if ( ret > 0 ) {
for ( i = 0; i<ret; i++ ) {
//if a unit shares a source input in a device, copy data.
if ( i> 0 && tuner_input_sharing( &ht[i-1], &ht[i] ) ) {
ht[i].cc_tuner = ht[i-1].cc_tuner;
strncpy( ht[i].model, ht[i-1].model, sizeof(ht[i].model) );
} else
get_dev_model( &ht[i] );
printf( "%d tuner:%s ip:%s type:%d cc:%d model:%s\n", i, ht[i].name, ht[i].ip, ht[i].type, ht[i].cc_tuner, ht[i].model );
}
tuner_count = ret;
}
// test channel scan
if ( 0 ) {
time_t t0 = time( NULL );
struct channel_entry_t ce[400];
int channel_num = 0;
//channel_num = scan_all( "101007FD-0", ce, 400 );
for ( i = 60; i<=70; i++ ) {
ret = scan_channel( "101007FD-1", i, ce, channel_num, 400 );
if ( ret < 0 || sigabort_flag )
break;
printf( "find channe:%d\n", ret );
channel_num += ret;
}
printf( "time:%ld\n", time( NULL) - t0 );
_save_channel_entry( "channel-scan.txt", ce, channel_num );
}
/*
//test vchannel scan
if ( 1 ) {
struct hdhr_tuner_t *cc_ht = NULL;
for ( i = 0; i<tuner_count; i++ ) {
if ( ht[i].cc_tuner ) {
cc_ht = &ht[i];
break;
}
}
if ( cc_ht != NULL ) {
time_t t0 = time( NULL );
struct vchan_tbl_t * vt = create_vchan_tbl( cc_ht );
ret = get_vchannel_list( vt );
if ( ret > 0 ) {
int n;
int channel_num = 0;
struct channel_list_t *cl = create_channel_list( ret );
for ( n = 1; n<vt->channel_num && channel_num < 500 ; n++ ) {
printf( "TRACE %d %s\n", n, vt->vchan[n].guide_name );
ret = scan_vchannel( vt, n, cl );
if ( ret > 0 ) {
struct channel_entry_t *ce_p = &cl->ce[channel_num];
snprintf( ce_p->dev_name, sizeof(ce_p->dev_name), "HDHR.%s", device, sizeof(ce_p->dev_name) );
channel_num++;
}
printf( "TRACE channel %d, %d\n", channel_num, n );
}
_save_channel_entry( "channel-scan.txt", cl );
}
release_vchan_tbl( vt );
printf( "time:%ld\n", time( NULL) - t0 );
}
}
*/
//test tune
if ( 1 ) {
//printf( "tuning %s \n", device[i] );
char *dev_name = "1311A273-0"; //"101007FD-1";
//ret = tune_channel( dev_name, "map:us-cable|auto:68", "343", "udp://226.0.0.1:4096" );
ret = tune_channel( dev_name, "vchan:600", "", "udp://226.0.0.1:4096" );
printf( "hit enter to stop\n" );
getchar();
ret = stop_channel( dev_name );
}
return ret;
}
#endif