blob: ed1ec432d55544d3a0ab1712aecf50abae2ce871 [file] [log] [blame]
/*
* hdhomerun_channelscan.c
*
* Copyright © 2007-2010 Silicondust USA Inc. <www.silicondust.com>.
*
* This library 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 3 of the License, or (at your option) any later version.
*
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception to the GNU Lesser General Public License,
* you may link, statically or dynamically, an application with a
* publicly distributed version of the Library to produce an
* executable file containing portions of the Library, and
* distribute that executable file under terms of your choice,
* without any of the additional requirements listed in clause 4 of
* the GNU Lesser General Public License.
*
* By "a publicly distributed version of the Library", we mean
* either the unmodified Library as distributed by Silicondust, or a
* modified version of the Library that is distributed under the
* conditions defined in the GNU Lesser General Public License.
*/
#include "hdhomerun.h"
struct hdhomerun_channelscan_t {
struct hdhomerun_device_t *hd;
uint32_t scanned_channels;
struct hdhomerun_channel_list_t *channel_list;
struct hdhomerun_channel_entry_t *next_channel;
};
struct hdhomerun_channelscan_t *channelscan_create(struct hdhomerun_device_t *hd, const char *channelmap)
{
struct hdhomerun_channelscan_t *scan = (struct hdhomerun_channelscan_t *)calloc(1, sizeof(struct hdhomerun_channelscan_t));
if (!scan) {
return NULL;
}
scan->hd = hd;
scan->channel_list = hdhomerun_channel_list_create(channelmap);
if (!scan->channel_list) {
free(scan);
return NULL;
}
scan->next_channel = hdhomerun_channel_list_last(scan->channel_list);
return scan;
}
void channelscan_destroy(struct hdhomerun_channelscan_t *scan)
{
free(scan);
}
static int channelscan_find_lock(struct hdhomerun_channelscan_t *scan, uint32_t frequency, struct hdhomerun_channelscan_result_t *result)
{
/* Set channel. */
char channel_str[64];
sprintf(channel_str, "auto:%ld", (unsigned long)frequency);
int ret = hdhomerun_device_set_tuner_channel(scan->hd, channel_str);
if (ret <= 0) {
return ret;
}
/* Wait for lock. */
ret = hdhomerun_device_wait_for_lock(scan->hd, &result->status);
if (ret <= 0) {
return ret;
}
if (!result->status.lock_supported) {
return 1;
}
/* Wait for symbol quality = 100%. */
uint64_t timeout = getcurrenttime() + 5000;
while (1) {
ret = hdhomerun_device_get_tuner_status(scan->hd, NULL, &result->status);
if (ret <= 0) {
return ret;
}
if (result->status.symbol_error_quality == 100) {
return 1;
}
if (getcurrenttime() >= timeout) {
return 1;
}
msleep_approx(250);
}
}
static void channelscan_extract_name(struct hdhomerun_channelscan_program_t *program, const char *line)
{
/* Find start of name. */
const char *start = strchr(line, ' ');
if (!start) {
return;
}
start++;
start = strchr(start, ' ');
if (!start) {
return;
}
start++;
/* Find end of name. */
const char *end = strstr(start, " (");
if (!end) {
end = strchr(line, 0);
}
if (end <= start) {
return;
}
/* Extract name. */
size_t length = (size_t)(end - start);
if (length > sizeof(program->name) - 1) {
length = sizeof(program->name) - 1;
}
strncpy(program->name, start, length);
program->name[length] = 0;
}
static int channelscan_detect_programs(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result, bool_t *pchanged, bool_t *pincomplete)
{
*pchanged = FALSE;
*pincomplete = FALSE;
char *streaminfo;
int ret = hdhomerun_device_get_tuner_streaminfo(scan->hd, &streaminfo);
if (ret <= 0) {
return ret;
}
char *next_line = streaminfo;
int program_count = 0;
while (1) {
char *line = next_line;
next_line = strchr(line, '\n');
if (!next_line) {
break;
}
*next_line++ = 0;
unsigned int transport_stream_id;
if (sscanf(line, "tsid=0x%x", &transport_stream_id) == 1) {
result->transport_stream_id = transport_stream_id;
result->transport_stream_id_detected = TRUE;
continue;
}
if (program_count >= HDHOMERUN_CHANNELSCAN_MAX_PROGRAM_COUNT) {
continue;
}
struct hdhomerun_channelscan_program_t program;
memset(&program, 0, sizeof(program));
strncpy(program.program_str, line, sizeof(program.program_str));
program.program_str[sizeof(program.program_str) - 1] = 0;
unsigned int program_number;
unsigned int virtual_major, virtual_minor;
if (sscanf(line, "%u: %u.%u", &program_number, &virtual_major, &virtual_minor) != 3) {
if (sscanf(line, "%u: %u", &program_number, &virtual_major) != 2) {
continue;
}
virtual_minor = 0;
}
program.program_number = program_number;
program.virtual_major = virtual_major;
program.virtual_minor = virtual_minor;
channelscan_extract_name(&program, line);
if (strstr(line, "(control)")) {
program.type = HDHOMERUN_CHANNELSCAN_PROGRAM_CONTROL;
} else if (strstr(line, "(encrypted)")) {
program.type = HDHOMERUN_CHANNELSCAN_PROGRAM_ENCRYPTED;
} else if (strstr(line, "(no data)")) {
program.type = HDHOMERUN_CHANNELSCAN_PROGRAM_NODATA;
*pincomplete = TRUE;
} else {
program.type = HDHOMERUN_CHANNELSCAN_PROGRAM_NORMAL;
if ((program.virtual_major == 0) || (program.name[0] == 0)) {
*pincomplete = TRUE;
}
}
if (memcmp(&result->programs[program_count], &program, sizeof(program)) != 0) {
memcpy(&result->programs[program_count], &program, sizeof(program));
*pchanged = TRUE;
}
program_count++;
}
if (program_count == 0) {
*pincomplete = TRUE;
}
if (result->program_count != program_count) {
result->program_count = program_count;
*pchanged = TRUE;
}
return 1;
}
int channelscan_advance(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result)
{
memset(result, 0, sizeof(struct hdhomerun_channelscan_result_t));
struct hdhomerun_channel_entry_t *entry = scan->next_channel;
if (!entry) {
return 0;
}
/* Combine channels with same frequency. */
result->frequency = hdhomerun_channel_entry_frequency(entry);
strncpy(result->channel_str, hdhomerun_channel_entry_name(entry), sizeof(result->channel_str) - 1);
result->channel_str[sizeof(result->channel_str) - 1] = 0;
while (1) {
entry = hdhomerun_channel_list_prev(scan->channel_list, entry);
if (!entry) {
scan->next_channel = NULL;
break;
}
if (hdhomerun_channel_entry_frequency(entry) != result->frequency) {
scan->next_channel = entry;
break;
}
char *ptr = strchr(result->channel_str, 0);
sprintf(ptr, ", %s", hdhomerun_channel_entry_name(entry));
}
return 1;
}
int channelscan_at(struct hdhomerun_channelscan_t *scan, int number, struct hdhomerun_channelscan_result_t *result)
{
memset(result, 0, sizeof(struct hdhomerun_channelscan_result_t));
struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_last( scan->channel_list );
while ( entry ) {
if ( hdhomerun_channel_entry_channel_number( entry ) == number )
break;
entry = hdhomerun_channel_list_prev(scan->channel_list, entry);
}
scan->next_channel = 0;
/* Combine channels with same frequency. */
result->frequency = hdhomerun_channel_entry_frequency(entry);
strncpy(result->channel_str, hdhomerun_channel_entry_name(entry), sizeof(result->channel_str) - 1);
result->channel_str[sizeof(result->channel_str) - 1] = 0;
char *ptr = strchr(result->channel_str, 0);
sprintf(ptr, ", %s", hdhomerun_channel_entry_name(entry));
return 1;
}
int channelscan_detect(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result)
{
scan->scanned_channels++;
/* Find lock. */
int ret = channelscan_find_lock(scan, result->frequency, result);
if (ret <= 0) {
return ret;
}
if (!result->status.lock_supported) {
return 1;
}
/* Detect programs. */
result->program_count = 0;
uint64_t timeout;
if (strstr(hdhomerun_device_get_model_str(scan->hd), "atsc")) {
timeout = getcurrenttime() + 4000;
} else {
timeout = getcurrenttime() + 10000;
}
uint64_t complete_time = getcurrenttime() + 1000;
while (1) {
bool_t changed, incomplete;
ret = channelscan_detect_programs(scan, result, &changed, &incomplete);
if (ret <= 0) {
return ret;
}
if (changed) {
complete_time = getcurrenttime() + 1000;
}
if (!incomplete && (getcurrenttime() >= complete_time)) {
break;
}
if (getcurrenttime() >= timeout) {
break;
}
msleep_approx(250);
}
/* Lock => skip overlapping channels. */
uint32_t max_next_frequency = result->frequency - 5500000;
while (1) {
if (!scan->next_channel) {
break;
}
if (hdhomerun_channel_entry_frequency(scan->next_channel) <= max_next_frequency) {
break;
}
scan->next_channel = hdhomerun_channel_list_prev(scan->channel_list, scan->next_channel);
}
/* Success. */
return 1;
}
uint8_t channelscan_get_progress(struct hdhomerun_channelscan_t *scan)
{
struct hdhomerun_channel_entry_t *entry = scan->next_channel;
if (!entry) {
return 100;
}
uint32_t channels_remaining = 1;
uint32_t frequency = hdhomerun_channel_entry_frequency(entry);
while (1) {
entry = hdhomerun_channel_list_prev(scan->channel_list, entry);
if (!entry) {
break;
}
if (hdhomerun_channel_entry_frequency(entry) != frequency) {
channels_remaining++;
frequency = hdhomerun_channel_entry_frequency(entry);
}
}
return scan->scanned_channels * 100 / (scan->scanned_channels + channels_remaining);
}