Initial import of libhdhomerun
http://download.silicondust.com/hdhomerun/libhdhomerun_20120405.tgz
Change-Id: Ie1fc20f22827fdcf007d9b2ad93552baf3df398b
diff --git a/libhdhomerun/Makefile b/libhdhomerun/Makefile
new file mode 100755
index 0000000..dfda53f
--- /dev/null
+++ b/libhdhomerun/Makefile
@@ -0,0 +1,59 @@
+
+LIBSRCS += hdhomerun_channels.c
+LIBSRCS += hdhomerun_channelscan.c
+LIBSRCS += hdhomerun_control.c
+LIBSRCS += hdhomerun_debug.c
+LIBSRCS += hdhomerun_device.c
+LIBSRCS += hdhomerun_device_selector.c
+LIBSRCS += hdhomerun_discover.c
+LIBSRCS += hdhomerun_os_posix.c
+LIBSRCS += hdhomerun_pkt.c
+LIBSRCS += hdhomerun_sock_posix.c
+LIBSRCS += hdhomerun_video.c
+
+CC := $(CROSS_COMPILE)gcc
+STRIP := $(CROSS_COMPILE)strip
+
+CFLAGS += -Wall -O2 -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wpointer-arith
+LDFLAGS += -lpthread
+SHARED = -shared -Wl,-soname,libhdhomerun$(LIBEXT)
+
+ifeq ($(OS),Windows_NT)
+ BINEXT := .exe
+ LIBEXT := .dll
+ LDFLAGS += -liphlpapi
+else
+ OS := $(shell uname -s)
+ LIBEXT := .so
+ ifeq ($(OS),Linux)
+ LDFLAGS += -lrt
+ endif
+ ifeq ($(OS),SunOS)
+ LDFLAGS += -lsocket
+ endif
+ ifeq ($(OS),Darwin)
+ CFLAGS += -arch i386 -arch ppc
+ LIBEXT := .dylib
+ SHARED := -dynamiclib -install_name libhdhomerun$(LIBEXT)
+ endif
+endif
+
+all : hdhomerun_config$(BINEXT) libhdhomerun$(LIBEXT)
+
+hdhomerun_config$(BINEXT) : hdhomerun_config.c $(LIBSRCS)
+ $(CC) $(CFLAGS) $+ $(LDFLAGS) -o $@
+ $(STRIP) $@
+
+libhdhomerun$(LIBEXT) : $(LIBSRCS)
+ $(CC) $(CFLAGS) -fPIC -DDLL_EXPORT $(SHARED) $+ $(LDFLAGS) -o $@
+
+clean :
+ -rm -f hdhomerun_config$(BINEXT)
+ -rm -f libhdhomerun$(LIBEXT)
+
+distclean : clean
+
+%:
+ @echo "(ignoring request to make $@)"
+
+.PHONY: all list clean distclean
diff --git a/libhdhomerun/README b/libhdhomerun/README
new file mode 100755
index 0000000..9b5d853
--- /dev/null
+++ b/libhdhomerun/README
@@ -0,0 +1,41 @@
+/*
+ * README
+ *
+ * Copyright © 2005-2009 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.
+ */
+
+Top level include file: hdhomerun.h
+
+Top level API: hdhomerun_device. See hdhomerun_device.h for documentation.
+
+The hdhomerun_device API should be used rather than the low level control and video APIs required with previous versions.
+
+Additional libraries required:
+- pthread
+- iphlpapi (windows only)
diff --git a/libhdhomerun/hdhomerun.h b/libhdhomerun/hdhomerun.h
new file mode 100755
index 0000000..affd4e2
--- /dev/null
+++ b/libhdhomerun/hdhomerun.h
@@ -0,0 +1,44 @@
+/*
+ * hdhomerun.h
+ *
+ * Copyright © 2006-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_os.h"
+#include "hdhomerun_types.h"
+#include "hdhomerun_pkt.h"
+#include "hdhomerun_sock.h"
+#include "hdhomerun_debug.h"
+#include "hdhomerun_discover.h"
+#include "hdhomerun_control.h"
+#include "hdhomerun_video.h"
+#include "hdhomerun_channels.h"
+#include "hdhomerun_channelscan.h"
+#include "hdhomerun_device.h"
+#include "hdhomerun_device_selector.h"
diff --git a/libhdhomerun/hdhomerun_channels.c b/libhdhomerun/hdhomerun_channels.c
new file mode 100755
index 0000000..c78ebce
--- /dev/null
+++ b/libhdhomerun/hdhomerun_channels.c
@@ -0,0 +1,399 @@
+/*
+ * hdhomerun_channels.c
+ *
+ * Copyright © 2007-2008 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_channel_entry_t {
+ struct hdhomerun_channel_entry_t *next;
+ struct hdhomerun_channel_entry_t *prev;
+ uint32_t frequency;
+ uint16_t channel_number;
+ char name[16];
+};
+
+struct hdhomerun_channel_list_t {
+ struct hdhomerun_channel_entry_t *head;
+ struct hdhomerun_channel_entry_t *tail;
+};
+
+struct hdhomerun_channelmap_range_t {
+ uint16_t channel_range_start;
+ uint16_t channel_range_end;
+ uint32_t frequency;
+ uint32_t spacing;
+};
+
+struct hdhomerun_channelmap_record_t {
+ const char *channelmap;
+ const struct hdhomerun_channelmap_range_t *range_list;
+ const char *channelmap_scan_group;
+ const char *countrycodes;
+};
+
+/* AU antenna channels. Channels {6, 7, 8, 9, 9A} are numbered {5, 6, 7, 8, 9} by the HDHomeRun. */
+static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_au_bcast[] = {
+ { 5, 12, 177500000, 7000000},
+ { 21, 69, 480500000, 7000000},
+ { 0, 0, 0, 0}
+};
+
+/* EU antenna channels. */
+static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_eu_bcast[] = {
+ { 5, 12, 177500000, 7000000},
+ { 21, 69, 474000000, 8000000},
+ { 0, 0, 0, 0}
+};
+
+/* EU cable channels. No common standard - use frequency in MHz for channel number. */
+static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_eu_cable[] = {
+ { 50, 998, 50000000, 1000000},
+ { 0, 0, 0, 0}
+};
+
+/* KR cable channels. */
+static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_kr_cable[] = {
+ { 2, 4, 57000000, 6000000},
+ { 5, 6, 79000000, 6000000},
+ { 7, 13, 177000000, 6000000},
+ { 14, 22, 123000000, 6000000},
+ { 23, 153, 219000000, 6000000},
+ { 0, 0, 0, 0}
+};
+
+/* US antenna channels. */
+static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_us_bcast[] = {
+ { 2, 4, 57000000, 6000000},
+ { 5, 6, 79000000, 6000000},
+ { 7, 13, 177000000, 6000000},
+ { 14, 69, 473000000, 6000000},
+ { 0, 0, 0, 0}
+};
+
+/* US cable channels. */
+static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_us_cable[] = {
+ { 2, 4, 57000000, 6000000},
+ { 5, 6, 79000000, 6000000},
+ { 7, 13, 177000000, 6000000},
+ { 14, 22, 123000000, 6000000},
+ { 23, 94, 219000000, 6000000},
+ { 95, 99, 93000000, 6000000},
+ {100, 158, 651000000, 6000000},
+ { 0, 0, 0, 0}
+};
+
+/* US cable channels (HRC). */
+static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_us_hrc[] = {
+ { 2, 4, 55752700, 6000300},
+ { 5, 6, 79753900, 6000300},
+ { 7, 13, 175758700, 6000300},
+ { 14, 22, 121756000, 6000300},
+ { 23, 94, 217760800, 6000300},
+ { 95, 99, 91754500, 6000300},
+ {100, 158, 649782400, 6000300},
+ { 0, 0, 0, 0}
+};
+
+/* US cable channels (IRC). */
+static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_us_irc[] = {
+ { 2, 4, 57012500, 6000000},
+ { 5, 6, 81012500, 6000000},
+ { 7, 13, 177012500, 6000000},
+ { 14, 22, 123012500, 6000000},
+ { 23, 41, 219012500, 6000000},
+ { 42, 42, 333025000, 6000000},
+ { 43, 94, 339012500, 6000000},
+ { 95, 97, 93012500, 6000000},
+ { 98, 99, 111025000, 6000000},
+ {100, 158, 651012500, 6000000},
+ { 0, 0, 0, 0}
+};
+
+static const struct hdhomerun_channelmap_record_t hdhomerun_channelmap_table[] = {
+ {"au-bcast", hdhomerun_channelmap_range_au_bcast, "au-bcast", "AU"},
+ {"au-cable", hdhomerun_channelmap_range_eu_cable, "au-cable", "AU"},
+ {"eu-bcast", hdhomerun_channelmap_range_eu_bcast, "eu-bcast", "EU PA"},
+ {"eu-cable", hdhomerun_channelmap_range_eu_cable, "eu-cable", "EU"},
+ {"tw-bcast", hdhomerun_channelmap_range_us_bcast, "tw-bcast", "TW"},
+ {"tw-cable", hdhomerun_channelmap_range_us_cable, "tw-cable", "TW"},
+
+ {"kr-bcast", hdhomerun_channelmap_range_us_bcast, "kr-bcast", "KR"},
+ {"kr-cable", hdhomerun_channelmap_range_kr_cable, "kr-cable", "KR"},
+ {"us-bcast", hdhomerun_channelmap_range_us_bcast, "us-bcast", "CA US"},
+ {"us-cable", hdhomerun_channelmap_range_us_cable, "us-cable us-hrc us-irc", "CA PA US"},
+ {"us-hrc", hdhomerun_channelmap_range_us_hrc , "us-cable us-hrc us-irc", "CA PA US"},
+ {"us-irc", hdhomerun_channelmap_range_us_irc, "us-cable us-hrc us-irc", "CA PA US"},
+
+ {NULL, NULL, NULL, NULL}
+};
+
+const char *hdhomerun_channelmap_get_channelmap_from_country_source(const char *countrycode, const char *source)
+{
+ bool_t country_found = FALSE;
+
+ const struct hdhomerun_channelmap_record_t *record = hdhomerun_channelmap_table;
+ while (record->channelmap) {
+ if (!strstr(record->countrycodes, countrycode)) {
+ record++;
+ continue;
+ }
+
+ if (strstr(record->channelmap, source)) {
+ return record->channelmap;
+ }
+
+ country_found = TRUE;
+ record++;
+ }
+
+ if (!country_found) {
+ return hdhomerun_channelmap_get_channelmap_from_country_source("EU", source);
+ }
+
+ return NULL;
+}
+
+const char *hdhomerun_channelmap_get_channelmap_scan_group(const char *channelmap)
+{
+ const struct hdhomerun_channelmap_record_t *record = hdhomerun_channelmap_table;
+ while (record->channelmap) {
+ if (strstr(channelmap, record->channelmap)) {
+ return record->channelmap_scan_group;
+ }
+ record++;
+ }
+
+ return NULL;
+}
+
+uint16_t hdhomerun_channel_entry_channel_number(struct hdhomerun_channel_entry_t *entry)
+{
+ return entry->channel_number;
+}
+
+uint32_t hdhomerun_channel_entry_frequency(struct hdhomerun_channel_entry_t *entry)
+{
+ return entry->frequency;
+}
+
+const char *hdhomerun_channel_entry_name(struct hdhomerun_channel_entry_t *entry)
+{
+ return entry->name;
+}
+
+struct hdhomerun_channel_entry_t *hdhomerun_channel_list_first(struct hdhomerun_channel_list_t *channel_list)
+{
+ return channel_list->head;
+}
+
+struct hdhomerun_channel_entry_t *hdhomerun_channel_list_last(struct hdhomerun_channel_list_t *channel_list)
+{
+ return channel_list->tail;
+}
+
+struct hdhomerun_channel_entry_t *hdhomerun_channel_list_next(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry)
+{
+ return entry->next;
+}
+
+struct hdhomerun_channel_entry_t *hdhomerun_channel_list_prev(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry)
+{
+ return entry->prev;
+}
+
+uint32_t hdhomerun_channel_list_total_count(struct hdhomerun_channel_list_t *channel_list)
+{
+ uint32_t count = 0;
+
+ struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_first(channel_list);
+ while (entry) {
+ count++;
+ entry = hdhomerun_channel_list_next(channel_list, entry);
+ }
+
+ return count;
+}
+
+uint32_t hdhomerun_channel_list_frequency_count(struct hdhomerun_channel_list_t *channel_list)
+{
+ uint32_t count = 0;
+ uint32_t last_frequency = 0;
+
+ struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_first(channel_list);
+ while (entry) {
+ if (entry->frequency != last_frequency) {
+ last_frequency = entry->frequency;
+ count++;
+ }
+
+ entry = hdhomerun_channel_list_next(channel_list, entry);
+ }
+
+ return count;
+}
+
+uint32_t hdhomerun_channel_frequency_round(uint32_t frequency, uint32_t resolution)
+{
+ frequency += resolution / 2;
+ return (frequency / resolution) * resolution;
+}
+
+uint32_t hdhomerun_channel_frequency_round_normal(uint32_t frequency)
+{
+ return hdhomerun_channel_frequency_round(frequency, 125000);
+}
+
+uint32_t hdhomerun_channel_number_to_frequency(struct hdhomerun_channel_list_t *channel_list, uint16_t channel_number)
+{
+ struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_first(channel_list);
+ while (entry) {
+ if (entry->channel_number == channel_number) {
+ return entry->frequency;
+ }
+
+ entry = hdhomerun_channel_list_next(channel_list, entry);
+ }
+
+ return 0;
+}
+
+uint16_t hdhomerun_channel_frequency_to_number(struct hdhomerun_channel_list_t *channel_list, uint32_t frequency)
+{
+ frequency = hdhomerun_channel_frequency_round_normal(frequency);
+
+ struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_first(channel_list);
+ while (entry) {
+ if (entry->frequency == frequency) {
+ return entry->channel_number;
+ }
+ if (entry->frequency > frequency) {
+ return 0;
+ }
+
+ entry = hdhomerun_channel_list_next(channel_list, entry);
+ }
+
+ return 0;
+}
+
+static void hdhomerun_channel_list_build_insert(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry)
+{
+ struct hdhomerun_channel_entry_t *prev = NULL;
+ struct hdhomerun_channel_entry_t *next = channel_list->head;
+
+ while (next) {
+ if (next->frequency > entry->frequency) {
+ break;
+ }
+
+ prev = next;
+ next = next->next;
+ }
+
+ entry->prev = prev;
+ entry->next = next;
+
+ if (prev) {
+ prev->next = entry;
+ } else {
+ channel_list->head = entry;
+ }
+
+ if (next) {
+ next->prev = entry;
+ } else {
+ channel_list->tail = entry;
+ }
+}
+
+static void hdhomerun_channel_list_build_range(struct hdhomerun_channel_list_t *channel_list, const char *channelmap, const struct hdhomerun_channelmap_range_t *range)
+{
+ uint16_t channel_number;
+ for (channel_number = range->channel_range_start; channel_number <= range->channel_range_end; channel_number++) {
+ struct hdhomerun_channel_entry_t *entry = (struct hdhomerun_channel_entry_t *)calloc(1, sizeof(struct hdhomerun_channel_entry_t));
+ if (!entry) {
+ return;
+ }
+
+ entry->channel_number = channel_number;
+ entry->frequency = range->frequency + ((uint32_t)(channel_number - range->channel_range_start) * range->spacing);
+ entry->frequency = hdhomerun_channel_frequency_round_normal(entry->frequency);
+ sprintf(entry->name, "%s:%u", channelmap, entry->channel_number);
+
+ hdhomerun_channel_list_build_insert(channel_list, entry);
+ }
+}
+
+static void hdhomerun_channel_list_build_ranges(struct hdhomerun_channel_list_t *channel_list, const struct hdhomerun_channelmap_record_t *record)
+{
+ const struct hdhomerun_channelmap_range_t *range = record->range_list;
+ while (range->frequency) {
+ hdhomerun_channel_list_build_range(channel_list, record->channelmap, range);
+ range++;
+ }
+}
+
+void hdhomerun_channel_list_destroy(struct hdhomerun_channel_list_t *channel_list)
+{
+ while (channel_list->head) {
+ struct hdhomerun_channel_entry_t *entry = channel_list->head;
+ channel_list->head = entry->next;
+ free(entry);
+ }
+
+ free(channel_list);
+}
+
+struct hdhomerun_channel_list_t *hdhomerun_channel_list_create(const char *channelmap)
+{
+ struct hdhomerun_channel_list_t *channel_list = (struct hdhomerun_channel_list_t *)calloc(1, sizeof(struct hdhomerun_channel_list_t));
+ if (!channel_list) {
+ return NULL;
+ }
+
+ const struct hdhomerun_channelmap_record_t *record = hdhomerun_channelmap_table;
+ while (record->channelmap) {
+ if (!strstr(channelmap, record->channelmap)) {
+ record++;
+ continue;
+ }
+
+ hdhomerun_channel_list_build_ranges(channel_list, record);
+ record++;
+ }
+
+ if (!channel_list->head) {
+ free(channel_list);
+ return NULL;
+ }
+
+ return channel_list;
+}
diff --git a/libhdhomerun/hdhomerun_channels.h b/libhdhomerun/hdhomerun_channels.h
new file mode 100755
index 0000000..7772ce8
--- /dev/null
+++ b/libhdhomerun/hdhomerun_channels.h
@@ -0,0 +1,64 @@
+/*
+ * hdhomerun_channels.h
+ *
+ * Copyright © 2007-2008 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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct hdhomerun_channel_entry_t;
+struct hdhomerun_channel_list_t;
+
+extern LIBTYPE const char *hdhomerun_channelmap_get_channelmap_from_country_source(const char *countrycode, const char *source);
+extern LIBTYPE const char *hdhomerun_channelmap_get_channelmap_scan_group(const char *channelmap);
+
+extern LIBTYPE uint16_t hdhomerun_channel_entry_channel_number(struct hdhomerun_channel_entry_t *entry);
+extern LIBTYPE uint32_t hdhomerun_channel_entry_frequency(struct hdhomerun_channel_entry_t *entry);
+extern LIBTYPE const char *hdhomerun_channel_entry_name(struct hdhomerun_channel_entry_t *entry);
+
+extern LIBTYPE struct hdhomerun_channel_list_t *hdhomerun_channel_list_create(const char *channelmap);
+extern LIBTYPE void hdhomerun_channel_list_destroy(struct hdhomerun_channel_list_t *channel_list);
+
+extern LIBTYPE struct hdhomerun_channel_entry_t *hdhomerun_channel_list_first(struct hdhomerun_channel_list_t *channel_list);
+extern LIBTYPE struct hdhomerun_channel_entry_t *hdhomerun_channel_list_last(struct hdhomerun_channel_list_t *channel_list);
+extern LIBTYPE struct hdhomerun_channel_entry_t *hdhomerun_channel_list_next(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry);
+extern LIBTYPE struct hdhomerun_channel_entry_t *hdhomerun_channel_list_prev(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry);
+extern LIBTYPE uint32_t hdhomerun_channel_list_total_count(struct hdhomerun_channel_list_t *channel_list);
+extern LIBTYPE uint32_t hdhomerun_channel_list_frequency_count(struct hdhomerun_channel_list_t *channel_list);
+
+extern LIBTYPE uint32_t hdhomerun_channel_frequency_round(uint32_t frequency, uint32_t resolution);
+extern LIBTYPE uint32_t hdhomerun_channel_frequency_round_normal(uint32_t frequency);
+extern LIBTYPE uint32_t hdhomerun_channel_number_to_frequency(struct hdhomerun_channel_list_t *channel_list, uint16_t channel_number);
+extern LIBTYPE uint16_t hdhomerun_channel_frequency_to_number(struct hdhomerun_channel_list_t *channel_list, uint32_t frequency);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_channelscan.c b/libhdhomerun/hdhomerun_channelscan.c
new file mode 100755
index 0000000..8c1f8db
--- /dev/null
+++ b/libhdhomerun/hdhomerun_channelscan.c
@@ -0,0 +1,350 @@
+/*
+ * 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_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);
+}
diff --git a/libhdhomerun/hdhomerun_channelscan.h b/libhdhomerun/hdhomerun_channelscan.h
new file mode 100755
index 0000000..8a1fac2
--- /dev/null
+++ b/libhdhomerun/hdhomerun_channelscan.h
@@ -0,0 +1,53 @@
+/*
+ * hdhomerun_channelscan.h
+ *
+ * Copyright © 2007-2008 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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HDHOMERUN_CHANNELSCAN_PROGRAM_NORMAL 0
+#define HDHOMERUN_CHANNELSCAN_PROGRAM_NODATA 1
+#define HDHOMERUN_CHANNELSCAN_PROGRAM_CONTROL 2
+#define HDHOMERUN_CHANNELSCAN_PROGRAM_ENCRYPTED 3
+
+struct hdhomerun_channelscan_t;
+
+extern LIBTYPE struct hdhomerun_channelscan_t *channelscan_create(struct hdhomerun_device_t *hd, const char *channelmap);
+extern LIBTYPE void channelscan_destroy(struct hdhomerun_channelscan_t *scan);
+
+extern LIBTYPE int channelscan_advance(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result);
+extern LIBTYPE int channelscan_detect(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result);
+extern LIBTYPE uint8_t channelscan_get_progress(struct hdhomerun_channelscan_t *scan);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_config.c b/libhdhomerun/hdhomerun_config.c
new file mode 100755
index 0000000..a88268e
--- /dev/null
+++ b/libhdhomerun/hdhomerun_config.c
@@ -0,0 +1,702 @@
+/*
+ * hdhomerun_config.c
+ *
+ * Copyright © 2006-2008 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"
+
+/*
+ * The console output format should be set to UTF-8, however in XP and Vista this breaks batch file processing.
+ * Attempting to restore on exit fails to restore if the program is terminated by the user.
+ * Solution - set the output format each printf.
+ */
+#if defined(__WINDOWS__)
+#define printf console_printf
+#define vprintf console_vprintf
+#endif
+
+static const char *appname;
+
+struct hdhomerun_device_t *hd;
+
+static int help(void)
+{
+ printf("Usage:\n");
+ printf("\t%s discover\n", appname);
+ printf("\t%s <id> get help\n", appname);
+ printf("\t%s <id> get <item>\n", appname);
+ printf("\t%s <id> set <item> <value>\n", appname);
+ printf("\t%s <id> scan <tuner> [<filename>]\n", appname);
+ printf("\t%s <id> save <tuner> <filename>\n", appname);
+ printf("\t%s <id> upgrade <filename>\n", appname);
+ return -1;
+}
+
+static void extract_appname(const char *argv0)
+{
+ const char *ptr = strrchr(argv0, '/');
+ if (ptr) {
+ argv0 = ptr + 1;
+ }
+ ptr = strrchr(argv0, '\\');
+ if (ptr) {
+ argv0 = ptr + 1;
+ }
+ appname = argv0;
+}
+
+static bool_t contains(const char *arg, const char *cmpstr)
+{
+ if (strcmp(arg, cmpstr) == 0) {
+ return TRUE;
+ }
+
+ if (*arg++ != '-') {
+ return FALSE;
+ }
+ if (*arg++ != '-') {
+ return FALSE;
+ }
+ if (strcmp(arg, cmpstr) == 0) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static uint32_t parse_ip_addr(const char *str)
+{
+ unsigned long a[4];
+ if (sscanf(str, "%lu.%lu.%lu.%lu", &a[0], &a[1], &a[2], &a[3]) != 4) {
+ return 0;
+ }
+
+ return (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0));
+}
+
+static int discover_print(char *target_ip_str)
+{
+ uint32_t target_ip = 0;
+ if (target_ip_str) {
+ target_ip = parse_ip_addr(target_ip_str);
+ if (target_ip == 0) {
+ fprintf(stderr, "invalid ip address: %s\n", target_ip_str);
+ return -1;
+ }
+ }
+
+ struct hdhomerun_discover_device_t result_list[64];
+ int count = hdhomerun_discover_find_devices_custom(target_ip, HDHOMERUN_DEVICE_TYPE_TUNER, HDHOMERUN_DEVICE_ID_WILDCARD, result_list, 64);
+ if (count < 0) {
+ fprintf(stderr, "error sending discover request\n");
+ return -1;
+ }
+ if (count == 0) {
+ printf("no devices found\n");
+ return 0;
+ }
+
+ int index;
+ for (index = 0; index < count; index++) {
+ struct hdhomerun_discover_device_t *result = &result_list[index];
+ printf("hdhomerun device %08lX found at %u.%u.%u.%u\n",
+ (unsigned long)result->device_id,
+ (unsigned int)(result->ip_addr >> 24) & 0x0FF, (unsigned int)(result->ip_addr >> 16) & 0x0FF,
+ (unsigned int)(result->ip_addr >> 8) & 0x0FF, (unsigned int)(result->ip_addr >> 0) & 0x0FF
+ );
+ }
+
+ return count;
+}
+
+static int cmd_get(const char *item)
+{
+ char *ret_value;
+ char *ret_error;
+ if (hdhomerun_device_get_var(hd, item, &ret_value, &ret_error) < 0) {
+ fprintf(stderr, "communication error sending request to hdhomerun device\n");
+ return -1;
+ }
+
+ if (ret_error) {
+ printf("%s\n", ret_error);
+ return 0;
+ }
+
+ printf("%s\n", ret_value);
+ return 1;
+}
+
+static int cmd_set_internal(const char *item, const char *value)
+{
+ char *ret_error;
+ if (hdhomerun_device_set_var(hd, item, value, NULL, &ret_error) < 0) {
+ fprintf(stderr, "communication error sending request to hdhomerun device\n");
+ return -1;
+ }
+
+ if (ret_error) {
+ printf("%s\n", ret_error);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int cmd_set(const char *item, const char *value)
+{
+ if (strcmp(value, "-") == 0) {
+ char *buffer = NULL;
+ size_t pos = 0;
+
+ while (1) {
+ buffer = (char *)realloc(buffer, pos + 1024);
+ if (!buffer) {
+ fprintf(stderr, "out of memory\n");
+ return -1;
+ }
+
+ size_t size = fread(buffer + pos, 1, 1024, stdin);
+ pos += size;
+
+ if (size < 1024) {
+ break;
+ }
+ }
+
+ buffer[pos] = 0;
+
+ int ret = cmd_set_internal(item, buffer);
+
+ free(buffer);
+ return ret;
+ }
+
+ return cmd_set_internal(item, value);
+}
+
+static volatile sig_atomic_t sigabort_flag = FALSE;
+static volatile sig_atomic_t siginfo_flag = FALSE;
+
+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
+}
+
+static void cmd_scan_printf(FILE *fp, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+ if (fp) {
+ va_list apc;
+ va_copy(apc, ap);
+
+ vfprintf(fp, fmt, apc);
+ fflush(fp);
+
+ va_end(apc);
+ }
+
+ vprintf(fmt, ap);
+ fflush(stdout);
+
+ va_end(ap);
+}
+
+static int cmd_scan(const char *tuner_str, const char *filename)
+{
+ if (hdhomerun_device_set_tuner_from_str(hd, tuner_str) <= 0) {
+ fprintf(stderr, "invalid tuner number\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;
+ }
+
+ FILE *fp = NULL;
+ if (filename) {
+ fp = fopen(filename, "w");
+ if (!fp) {
+ fprintf(stderr, "unable to create file: %s\n", filename);
+ return -1;
+ }
+ }
+
+ register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler);
+
+ int ret = 0;
+ while (!sigabort_flag) {
+ struct hdhomerun_channelscan_result_t result;
+ ret = hdhomerun_device_channelscan_advance(hd, &result);
+ if (ret <= 0) {
+ break;
+ }
+
+ cmd_scan_printf(fp, "SCANNING: %lu (%s)\n",
+ (unsigned long)result.frequency, result.channel_str
+ );
+
+ ret = hdhomerun_device_channelscan_detect(hd, &result);
+ if (ret < 0) {
+ break;
+ }
+ if (ret == 0) {
+ continue;
+ }
+
+ cmd_scan_printf(fp, "LOCK: %s (ss=%u snq=%u seq=%u)\n",
+ result.status.lock_str, result.status.signal_strength,
+ result.status.signal_to_noise_quality, result.status.symbol_error_quality
+ );
+
+ if (result.transport_stream_id_detected) {
+ cmd_scan_printf(fp, "TSID: 0x%04X\n", result.transport_stream_id);
+ }
+
+ int i;
+ for (i = 0; i < result.program_count; i++) {
+ struct hdhomerun_channelscan_program_t *program = &result.programs[i];
+ cmd_scan_printf(fp, "PROGRAM %s\n", program->program_str);
+ }
+ }
+
+ hdhomerun_device_tuner_lockkey_release(hd);
+
+ if (fp) {
+ fclose(fp);
+ }
+ if (ret < 0) {
+ fprintf(stderr, "communication error sending request to hdhomerun device\n");
+ }
+ return ret;
+}
+
+static void cmd_save_print_stats(void)
+{
+ struct hdhomerun_video_stats_t stats;
+ hdhomerun_device_get_video_stats(hd, &stats);
+
+ fprintf(stderr, "%u packets received, %u overflow errors, %u network errors, %u transport errors, %u sequence errors\n",
+ (unsigned int)stats.packet_count,
+ (unsigned int)stats.overflow_error_count,
+ (unsigned int)stats.network_error_count,
+ (unsigned int)stats.transport_error_count,
+ (unsigned int)stats.sequence_error_count
+ );
+}
+
+static int cmd_save(const char *tuner_str, const char *filename)
+{
+ if (hdhomerun_device_set_tuner_from_str(hd, tuner_str) <= 0) {
+ fprintf(stderr, "invalid tuner number\n");
+ return -1;
+ }
+
+ FILE *fp;
+ if (strcmp(filename, "null") == 0) {
+ fp = NULL;
+ } else if (strcmp(filename, "-") == 0) {
+ fp = stdout;
+ } else {
+ fp = fopen(filename, "wb");
+ if (!fp) {
+ fprintf(stderr, "unable to create file %s\n", filename);
+ return -1;
+ }
+ }
+
+ int ret = hdhomerun_device_stream_start(hd);
+ if (ret <= 0) {
+ fprintf(stderr, "unable to start stream\n");
+ if (fp && fp != stdout) {
+ fclose(fp);
+ }
+ return ret;
+ }
+
+ register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler);
+
+ struct hdhomerun_video_stats_t stats_old, stats_cur;
+ hdhomerun_device_get_video_stats(hd, &stats_old);
+
+ uint64_t next_progress = getcurrenttime() + 1000;
+
+ while (!sigabort_flag) {
+ uint64_t loop_start_time = getcurrenttime();
+
+ if (siginfo_flag) {
+ fprintf(stderr, "\n");
+ cmd_save_print_stats();
+ siginfo_flag = FALSE;
+ }
+
+ size_t actual_size;
+ uint8_t *ptr = hdhomerun_device_stream_recv(hd, VIDEO_DATA_BUFFER_SIZE_1S, &actual_size);
+ if (!ptr) {
+ msleep_approx(64);
+ continue;
+ }
+
+ if (fp) {
+ if (fwrite(ptr, 1, actual_size, fp) != actual_size) {
+ fprintf(stderr, "error writing output\n");
+ return -1;
+ }
+ }
+
+ if (loop_start_time >= next_progress) {
+ next_progress += 1000;
+ if (loop_start_time >= next_progress) {
+ next_progress = loop_start_time + 1000;
+ }
+
+ /* Windows - indicate activity to suppress auto sleep mode. */
+ #if defined(__WINDOWS__)
+ SetThreadExecutionState(ES_SYSTEM_REQUIRED);
+ #endif
+
+ /* Video stats. */
+ hdhomerun_device_get_video_stats(hd, &stats_cur);
+
+ if (stats_cur.overflow_error_count > stats_old.overflow_error_count) {
+ fprintf(stderr, "o");
+ } else if (stats_cur.network_error_count > stats_old.network_error_count) {
+ fprintf(stderr, "n");
+ } else if (stats_cur.transport_error_count > stats_old.transport_error_count) {
+ fprintf(stderr, "t");
+ } else if (stats_cur.sequence_error_count > stats_old.sequence_error_count) {
+ fprintf(stderr, "s");
+ } else {
+ fprintf(stderr, ".");
+ }
+
+ stats_old = stats_cur;
+ fflush(stderr);
+ }
+
+ int32_t delay = 64 - (int32_t)(getcurrenttime() - loop_start_time);
+ if (delay <= 0) {
+ continue;
+ }
+
+ msleep_approx(delay);
+ }
+
+ if (fp) {
+ fclose(fp);
+ }
+
+ hdhomerun_device_stream_stop(hd);
+
+ fprintf(stderr, "\n");
+ fprintf(stderr, "-- Video statistics --\n");
+ cmd_save_print_stats();
+
+ return 0;
+}
+
+static int cmd_upgrade(const char *filename)
+{
+ FILE *fp = fopen(filename, "rb");
+ if (!fp) {
+ fprintf(stderr, "unable to open file %s\n", filename);
+ return -1;
+ }
+
+ printf("uploading firmware...\n");
+ if (hdhomerun_device_upgrade(hd, fp) <= 0) {
+ fprintf(stderr, "error sending upgrade file to hdhomerun device\n");
+ fclose(fp);
+ return -1;
+ }
+
+ fclose(fp);
+ msleep_minimum(2000);
+
+ printf("upgrading firmware...\n");
+ msleep_minimum(8000);
+
+ printf("rebooting...\n");
+ int count = 0;
+ char *version_str;
+ while (1) {
+ if (hdhomerun_device_get_version(hd, &version_str, NULL) >= 0) {
+ break;
+ }
+
+ count++;
+ if (count > 30) {
+ fprintf(stderr, "error finding device after firmware upgrade\n");
+ return -1;
+ }
+
+ msleep_minimum(1000);
+ }
+
+ printf("upgrade complete - now running firmware %s\n", version_str);
+ return 0;
+}
+
+static int cmd_execute(void)
+{
+ char *ret_value;
+ char *ret_error;
+ if (hdhomerun_device_get_var(hd, "/sys/boot", &ret_value, &ret_error) < 0) {
+ fprintf(stderr, "communication error sending request to hdhomerun device\n");
+ return -1;
+ }
+
+ if (ret_error) {
+ printf("%s\n", ret_error);
+ return 0;
+ }
+
+ char *end = ret_value + strlen(ret_value);
+ char *pos = ret_value;
+
+ while (1) {
+ if (pos >= end) {
+ break;
+ }
+
+ char *eol_r = strchr(pos, '\r');
+ if (!eol_r) {
+ eol_r = end;
+ }
+
+ char *eol_n = strchr(pos, '\n');
+ if (!eol_n) {
+ eol_n = end;
+ }
+
+ char *eol = eol_r;
+ if (eol_n < eol) {
+ eol = eol_n;
+ }
+
+ char *sep = strchr(pos, ' ');
+ if (!sep || sep > eol) {
+ pos = eol + 1;
+ continue;
+ }
+
+ *sep = 0;
+ *eol = 0;
+
+ char *item = pos;
+ char *value = sep + 1;
+
+ printf("set %s \"%s\"\n", item, value);
+
+ cmd_set_internal(item, value);
+
+ pos = eol + 1;
+ }
+
+ return 1;
+}
+
+static int main_cmd(int argc, char *argv[])
+{
+ if (argc < 1) {
+ return help();
+ }
+
+ char *cmd = *argv++; argc--;
+
+ if (contains(cmd, "key")) {
+ if (argc < 2) {
+ return help();
+ }
+ uint32_t lockkey = strtoul(argv[0], NULL, 0);
+ hdhomerun_device_tuner_lockkey_use_value(hd, lockkey);
+
+ cmd = argv[1];
+ argv+=2; argc-=2;
+ }
+
+ if (contains(cmd, "get")) {
+ if (argc < 1) {
+ return help();
+ }
+ return cmd_get(argv[0]);
+ }
+
+ if (contains(cmd, "set")) {
+ if (argc < 2) {
+ return help();
+ }
+ return cmd_set(argv[0], argv[1]);
+ }
+
+ if (contains(cmd, "scan")) {
+ if (argc < 1) {
+ return help();
+ }
+ if (argc < 2) {
+ return cmd_scan(argv[0], NULL);
+ } else {
+ return cmd_scan(argv[0], argv[1]);
+ }
+ }
+
+ if (contains(cmd, "save")) {
+ if (argc < 2) {
+ return help();
+ }
+ return cmd_save(argv[0], argv[1]);
+ }
+
+ if (contains(cmd, "upgrade")) {
+ if (argc < 1) {
+ return help();
+ }
+ return cmd_upgrade(argv[0]);
+ }
+
+ if (contains(cmd, "execute")) {
+ return cmd_execute();
+ }
+
+ return help();
+}
+
+static int main_internal(int argc, char *argv[])
+{
+#if defined(__WINDOWS__)
+ /* Initialize network socket support. */
+ WORD wVersionRequested = MAKEWORD(2, 0);
+ WSADATA wsaData;
+ WSAStartup(wVersionRequested, &wsaData);
+#endif
+
+ extract_appname(argv[0]);
+ argv++;
+ argc--;
+
+ if (argc == 0) {
+ return help();
+ }
+
+ char *id_str = *argv++; argc--;
+ if (contains(id_str, "help")) {
+ return help();
+ }
+ if (contains(id_str, "discover")) {
+ if (argc < 1) {
+ return discover_print(NULL);
+ } else {
+ return discover_print(argv[0]);
+ }
+ }
+
+ /* Device object. */
+ hd = hdhomerun_device_create_from_str(id_str, NULL);
+ if (!hd) {
+ fprintf(stderr, "invalid device id: %s\n", id_str);
+ return -1;
+ }
+
+ /* Device ID check. */
+ uint32_t device_id_requested = hdhomerun_device_get_device_id_requested(hd);
+ if (!hdhomerun_discover_validate_device_id(device_id_requested)) {
+ fprintf(stderr, "invalid device id: %08lX\n", (unsigned long)device_id_requested);
+ }
+
+ /* Connect to device and check model. */
+ const char *model = hdhomerun_device_get_model_str(hd);
+ if (!model) {
+ fprintf(stderr, "unable to connect to device\n");
+ hdhomerun_device_destroy(hd);
+ return -1;
+ }
+
+ /* Command. */
+ int ret = main_cmd(argc, argv);
+
+ /* Cleanup. */
+ hdhomerun_device_destroy(hd);
+
+ /* Complete. */
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = main_internal(argc, argv);
+ if (ret <= 0) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/libhdhomerun/hdhomerun_control.c b/libhdhomerun/hdhomerun_control.c
new file mode 100755
index 0000000..2873e53
--- /dev/null
+++ b/libhdhomerun/hdhomerun_control.c
@@ -0,0 +1,431 @@
+/*
+ * hdhomerun_control.c
+ *
+ * Copyright © 2006-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"
+
+#define HDHOMERUN_CONTROL_CONNECT_TIMEOUT 2500
+#define HDHOMERUN_CONTROL_SEND_TIMEOUT 2500
+#define HDHOMERUN_CONTROL_RECV_TIMEOUT 2500
+#define HDHOMERUN_CONTROL_UPGRADE_TIMEOUT 20000
+
+struct hdhomerun_control_sock_t {
+ uint32_t desired_device_id;
+ uint32_t desired_device_ip;
+ uint32_t actual_device_id;
+ uint32_t actual_device_ip;
+ hdhomerun_sock_t sock;
+ struct hdhomerun_debug_t *dbg;
+ struct hdhomerun_pkt_t tx_pkt;
+ struct hdhomerun_pkt_t rx_pkt;
+};
+
+static void hdhomerun_control_close_sock(struct hdhomerun_control_sock_t *cs)
+{
+ if (cs->sock == HDHOMERUN_SOCK_INVALID) {
+ return;
+ }
+
+ hdhomerun_sock_destroy(cs->sock);
+ cs->sock = HDHOMERUN_SOCK_INVALID;
+}
+
+void hdhomerun_control_set_device(struct hdhomerun_control_sock_t *cs, uint32_t device_id, uint32_t device_ip)
+{
+ hdhomerun_control_close_sock(cs);
+
+ cs->desired_device_id = device_id;
+ cs->desired_device_ip = device_ip;
+ cs->actual_device_id = 0;
+ cs->actual_device_ip = 0;
+}
+
+struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, uint32_t device_ip, struct hdhomerun_debug_t *dbg)
+{
+ struct hdhomerun_control_sock_t *cs = (struct hdhomerun_control_sock_t *)calloc(1, sizeof(struct hdhomerun_control_sock_t));
+ if (!cs) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_control_create: failed to allocate control object\n");
+ return NULL;
+ }
+
+ cs->dbg = dbg;
+ cs->sock = HDHOMERUN_SOCK_INVALID;
+ hdhomerun_control_set_device(cs, device_id, device_ip);
+
+ return cs;
+}
+
+void hdhomerun_control_destroy(struct hdhomerun_control_sock_t *cs)
+{
+ hdhomerun_control_close_sock(cs);
+ free(cs);
+}
+
+static bool_t hdhomerun_control_connect_sock(struct hdhomerun_control_sock_t *cs)
+{
+ if (cs->sock != HDHOMERUN_SOCK_INVALID) {
+ return TRUE;
+ }
+
+ if ((cs->desired_device_id == 0) && (cs->desired_device_ip == 0)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: no device specified\n");
+ return FALSE;
+ }
+ if (hdhomerun_discover_is_ip_multicast(cs->desired_device_ip)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: cannot use multicast ip address for device operations\n");
+ return FALSE;
+ }
+
+ /* Find device. */
+ struct hdhomerun_discover_device_t result;
+ if (hdhomerun_discover_find_devices_custom(cs->desired_device_ip, HDHOMERUN_DEVICE_TYPE_WILDCARD, cs->desired_device_id, &result, 1) <= 0) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: device not found\n");
+ return FALSE;
+ }
+ cs->actual_device_ip = result.ip_addr;
+ cs->actual_device_id = result.device_id;
+
+ /* Create socket. */
+ cs->sock = hdhomerun_sock_create_tcp();
+ if (cs->sock == HDHOMERUN_SOCK_INVALID) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: failed to create socket (%d)\n", hdhomerun_sock_getlasterror());
+ return FALSE;
+ }
+
+ /* Initiate connection. */
+ if (!hdhomerun_sock_connect(cs->sock, cs->actual_device_ip, HDHOMERUN_CONTROL_TCP_PORT, HDHOMERUN_CONTROL_CONNECT_TIMEOUT)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: failed to connect (%d)\n", hdhomerun_sock_getlasterror());
+ hdhomerun_control_close_sock(cs);
+ return FALSE;
+ }
+
+ /* Success. */
+ return TRUE;
+}
+
+uint32_t hdhomerun_control_get_device_id(struct hdhomerun_control_sock_t *cs)
+{
+ if (!hdhomerun_control_connect_sock(cs)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_device_id: connect failed\n");
+ return 0;
+ }
+
+ return cs->actual_device_id;
+}
+
+uint32_t hdhomerun_control_get_device_ip(struct hdhomerun_control_sock_t *cs)
+{
+ if (!hdhomerun_control_connect_sock(cs)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_device_ip: connect failed\n");
+ return 0;
+ }
+
+ return cs->actual_device_ip;
+}
+
+uint32_t hdhomerun_control_get_device_id_requested(struct hdhomerun_control_sock_t *cs)
+{
+ return cs->desired_device_id;
+}
+
+uint32_t hdhomerun_control_get_device_ip_requested(struct hdhomerun_control_sock_t *cs)
+{
+ return cs->desired_device_ip;
+}
+
+uint32_t hdhomerun_control_get_local_addr(struct hdhomerun_control_sock_t *cs)
+{
+ if (!hdhomerun_control_connect_sock(cs)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_local_addr: connect failed\n");
+ return 0;
+ }
+
+ uint32_t addr = hdhomerun_sock_getsockname_addr(cs->sock);
+ if (addr == 0) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_local_addr: getsockname failed (%d)\n", hdhomerun_sock_getlasterror());
+ return 0;
+ }
+
+ return addr;
+}
+
+static bool_t hdhomerun_control_send_sock(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt)
+{
+ if (!hdhomerun_sock_send(cs->sock, tx_pkt->start, tx_pkt->end - tx_pkt->start, HDHOMERUN_CONTROL_SEND_TIMEOUT)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_send_sock: send failed (%d)\n", hdhomerun_sock_getlasterror());
+ hdhomerun_control_close_sock(cs);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool_t hdhomerun_control_recv_sock(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *rx_pkt, uint16_t *ptype, uint64_t recv_timeout)
+{
+ uint64_t stop_time = getcurrenttime() + recv_timeout;
+ hdhomerun_pkt_reset(rx_pkt);
+
+ while (1) {
+ uint64_t current_time = getcurrenttime();
+ if (current_time >= stop_time) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_recv_sock: timeout\n");
+ hdhomerun_control_close_sock(cs);
+ return FALSE;
+ }
+
+ size_t length = rx_pkt->limit - rx_pkt->end;
+ if (!hdhomerun_sock_recv(cs->sock, rx_pkt->end, &length, stop_time - current_time)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_recv_sock: recv failed (%d)\n", hdhomerun_sock_getlasterror());
+ hdhomerun_control_close_sock(cs);
+ return FALSE;
+ }
+
+ rx_pkt->end += length;
+
+ int ret = hdhomerun_pkt_open_frame(rx_pkt, ptype);
+ if (ret < 0) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_recv_sock: frame error\n");
+ hdhomerun_control_close_sock(cs);
+ return FALSE;
+ }
+ if (ret > 0) {
+ return TRUE;
+ }
+ }
+}
+
+static int hdhomerun_control_send_recv_internal(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt, struct hdhomerun_pkt_t *rx_pkt, uint16_t type, uint64_t recv_timeout)
+{
+ hdhomerun_pkt_seal_frame(tx_pkt, type);
+
+ int i;
+ for (i = 0; i < 2; i++) {
+ if (cs->sock == HDHOMERUN_SOCK_INVALID) {
+ if (!hdhomerun_control_connect_sock(cs)) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_send_recv: connect failed\n");
+ return -1;
+ }
+ }
+
+ if (!hdhomerun_control_send_sock(cs, tx_pkt)) {
+ continue;
+ }
+ if (!rx_pkt) {
+ return 1;
+ }
+
+ uint16_t rsp_type;
+ if (!hdhomerun_control_recv_sock(cs, rx_pkt, &rsp_type, recv_timeout)) {
+ continue;
+ }
+ if (rsp_type != type + 1) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_send_recv: unexpected frame type\n");
+ hdhomerun_control_close_sock(cs);
+ continue;
+ }
+
+ return 1;
+ }
+
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_send_recv: failed\n");
+ return -1;
+}
+
+int hdhomerun_control_send_recv(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt, struct hdhomerun_pkt_t *rx_pkt, uint16_t type)
+{
+ return hdhomerun_control_send_recv_internal(cs, tx_pkt, rx_pkt, type, HDHOMERUN_CONTROL_RECV_TIMEOUT);
+}
+
+static int hdhomerun_control_get_set(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, uint32_t lockkey, char **pvalue, char **perror)
+{
+ struct hdhomerun_pkt_t *tx_pkt = &cs->tx_pkt;
+ struct hdhomerun_pkt_t *rx_pkt = &cs->rx_pkt;
+
+ /* Request. */
+ hdhomerun_pkt_reset(tx_pkt);
+
+ int name_len = (int)strlen(name) + 1;
+ if (tx_pkt->end + 3 + name_len > tx_pkt->limit) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: request too long\n");
+ return -1;
+ }
+ hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_GETSET_NAME);
+ hdhomerun_pkt_write_var_length(tx_pkt, name_len);
+ hdhomerun_pkt_write_mem(tx_pkt, (const void *)name, name_len);
+
+ if (value) {
+ int value_len = (int)strlen(value) + 1;
+ if (tx_pkt->end + 3 + value_len > tx_pkt->limit) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: request too long\n");
+ return -1;
+ }
+ hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_GETSET_VALUE);
+ hdhomerun_pkt_write_var_length(tx_pkt, value_len);
+ hdhomerun_pkt_write_mem(tx_pkt, (const void *)value, value_len);
+ }
+
+ if (lockkey != 0) {
+ if (tx_pkt->end + 6 > tx_pkt->limit) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: request too long\n");
+ return -1;
+ }
+ hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_GETSET_LOCKKEY);
+ hdhomerun_pkt_write_var_length(tx_pkt, 4);
+ hdhomerun_pkt_write_u32(tx_pkt, lockkey);
+ }
+
+ /* Send/Recv. */
+ if (hdhomerun_control_send_recv_internal(cs, tx_pkt, rx_pkt, HDHOMERUN_TYPE_GETSET_REQ, HDHOMERUN_CONTROL_RECV_TIMEOUT) < 0) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: send/recv error\n");
+ return -1;
+ }
+
+ /* Response. */
+ while (1) {
+ uint8_t tag;
+ size_t len;
+ uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len);
+ if (!next) {
+ break;
+ }
+
+ switch (tag) {
+ case HDHOMERUN_TAG_GETSET_VALUE:
+ if (pvalue) {
+ *pvalue = (char *)rx_pkt->pos;
+ rx_pkt->pos[len] = 0;
+ }
+ if (perror) {
+ *perror = NULL;
+ }
+ return 1;
+
+ case HDHOMERUN_TAG_ERROR_MESSAGE:
+ rx_pkt->pos[len] = 0;
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: %s\n", rx_pkt->pos);
+
+ if (pvalue) {
+ *pvalue = NULL;
+ }
+ if (perror) {
+ *perror = (char *)rx_pkt->pos;
+ }
+
+ return 0;
+ }
+
+ rx_pkt->pos = next;
+ }
+
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: missing response tags\n");
+ return -1;
+}
+
+int hdhomerun_control_get(struct hdhomerun_control_sock_t *cs, const char *name, char **pvalue, char **perror)
+{
+ return hdhomerun_control_get_set(cs, name, NULL, 0, pvalue, perror);
+}
+
+int hdhomerun_control_set(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, char **pvalue, char **perror)
+{
+ return hdhomerun_control_get_set(cs, name, value, 0, pvalue, perror);
+}
+
+int hdhomerun_control_set_with_lockkey(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, uint32_t lockkey, char **pvalue, char **perror)
+{
+ return hdhomerun_control_get_set(cs, name, value, lockkey, pvalue, perror);
+}
+
+int hdhomerun_control_upgrade(struct hdhomerun_control_sock_t *cs, FILE *upgrade_file)
+{
+ struct hdhomerun_pkt_t *tx_pkt = &cs->tx_pkt;
+ struct hdhomerun_pkt_t *rx_pkt = &cs->rx_pkt;
+ uint32_t sequence = 0;
+
+ /* Upload. */
+ while (1) {
+ uint8_t data[256];
+ size_t length = fread(data, 1, 256, upgrade_file);
+ if (length == 0) {
+ break;
+ }
+
+ hdhomerun_pkt_reset(tx_pkt);
+ hdhomerun_pkt_write_u32(tx_pkt, sequence);
+ hdhomerun_pkt_write_mem(tx_pkt, data, length);
+
+ if (hdhomerun_control_send_recv_internal(cs, tx_pkt, NULL, HDHOMERUN_TYPE_UPGRADE_REQ, 0) < 0) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_upgrade: send/recv failed\n");
+ return -1;
+ }
+
+ sequence += (uint32_t)length;
+ }
+
+ if (sequence == 0) {
+ /* No data in file. Error, but no need to close connection. */
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_upgrade: zero length file\n");
+ return 0;
+ }
+
+ /* Execute upgrade. */
+ hdhomerun_pkt_reset(tx_pkt);
+ hdhomerun_pkt_write_u32(tx_pkt, 0xFFFFFFFF);
+
+ if (hdhomerun_control_send_recv_internal(cs, tx_pkt, rx_pkt, HDHOMERUN_TYPE_UPGRADE_REQ, HDHOMERUN_CONTROL_UPGRADE_TIMEOUT) < 0) {
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_upgrade: send/recv failed\n");
+ return -1;
+ }
+
+ /* Check response. */
+ while (1) {
+ uint8_t tag;
+ size_t len;
+ uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len);
+ if (!next) {
+ break;
+ }
+
+ switch (tag) {
+ case HDHOMERUN_TAG_ERROR_MESSAGE:
+ rx_pkt->pos[len] = 0;
+ hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_upgrade: %s\n", (char *)rx_pkt->pos);
+ return 0;
+
+ default:
+ break;
+ }
+
+ rx_pkt->pos = next;
+ }
+
+ return 1;
+}
diff --git a/libhdhomerun/hdhomerun_control.h b/libhdhomerun/hdhomerun_control.h
new file mode 100755
index 0000000..9960066
--- /dev/null
+++ b/libhdhomerun/hdhomerun_control.h
@@ -0,0 +1,115 @@
+/*
+ * hdhomerun_control.h
+ *
+ * Copyright © 2006 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.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct hdhomerun_control_sock_t;
+
+/*
+ * Create a control socket.
+ *
+ * This function will not attempt to connect to the device.
+ * The connection will be established when first used.
+ *
+ * uint32_t device_id = 32-bit device id of device. Set to HDHOMERUN_DEVICE_ID_WILDCARD to match any device ID.
+ * uint32_t device_ip = IP address of device. Set to 0 to auto-detect.
+ * struct hdhomerun_debug_t *dbg: Pointer to debug logging object. May be NULL.
+ *
+ * Returns a pointer to the newly created control socket.
+ *
+ * When no longer needed, the socket should be destroyed by calling hdhomerun_control_destroy.
+ */
+extern LIBTYPE struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, uint32_t device_ip, struct hdhomerun_debug_t *dbg);
+extern LIBTYPE void hdhomerun_control_destroy(struct hdhomerun_control_sock_t *cs);
+
+/*
+ * Get the actual device id or ip of the device.
+ *
+ * Returns 0 if the device id cannot be determined.
+ */
+extern LIBTYPE uint32_t hdhomerun_control_get_device_id(struct hdhomerun_control_sock_t *cs);
+extern LIBTYPE uint32_t hdhomerun_control_get_device_ip(struct hdhomerun_control_sock_t *cs);
+extern LIBTYPE uint32_t hdhomerun_control_get_device_id_requested(struct hdhomerun_control_sock_t *cs);
+extern LIBTYPE uint32_t hdhomerun_control_get_device_ip_requested(struct hdhomerun_control_sock_t *cs);
+
+extern LIBTYPE void hdhomerun_control_set_device(struct hdhomerun_control_sock_t *cs, uint32_t device_id, uint32_t device_ip);
+
+/*
+ * Get the local machine IP address used when communicating with the device.
+ *
+ * This function is useful for determining the IP address to use with set target commands.
+ *
+ * Returns 32-bit IP address with native endianness, or 0 on error.
+ */
+extern LIBTYPE uint32_t hdhomerun_control_get_local_addr(struct hdhomerun_control_sock_t *cs);
+
+/*
+ * Low-level communication.
+ */
+extern LIBTYPE int hdhomerun_control_send_recv(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt, struct hdhomerun_pkt_t *rx_pkt, uint16_t type);
+
+/*
+ * Get/set a control variable on the device.
+ *
+ * const char *name: The name of var to get/set (c-string). The supported vars is device/firmware dependant.
+ * const char *value: The value to set (c-string). The format is device/firmware dependant.
+
+ * char **pvalue: If provided, the caller-supplied char pointer will be populated with a pointer to the value
+ * string returned by the device, or NULL if the device returned an error string. The string will remain
+ * valid until the next call to a control sock function.
+ * char **perror: If provided, the caller-supplied char pointer will be populated with a pointer to the error
+ * string returned by the device, or NULL if the device returned an value string. The string will remain
+ * valid until the next call to a control sock function.
+ *
+ * Returns 1 if the operation was successful (pvalue set, perror NULL).
+ * Returns 0 if the operation was rejected (pvalue NULL, perror set).
+ * Returns -1 if a communication error occurs.
+ */
+extern LIBTYPE int hdhomerun_control_get(struct hdhomerun_control_sock_t *cs, const char *name, char **pvalue, char **perror);
+extern LIBTYPE int hdhomerun_control_set(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, char **pvalue, char **perror);
+extern LIBTYPE int hdhomerun_control_set_with_lockkey(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, uint32_t lockkey, char **pvalue, char **perror);
+
+/*
+ * Upload new firmware to the device.
+ *
+ * FILE *upgrade_file: File pointer to read from. The file must have been opened in binary mode for reading.
+ *
+ * Returns 1 if the upload succeeded.
+ * Returns 0 if the upload was rejected.
+ * Returns -1 if an error occurs.
+ */
+extern LIBTYPE int hdhomerun_control_upgrade(struct hdhomerun_control_sock_t *cs, FILE *upgrade_file);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_debug.c b/libhdhomerun/hdhomerun_debug.c
new file mode 100755
index 0000000..9686f61
--- /dev/null
+++ b/libhdhomerun/hdhomerun_debug.c
@@ -0,0 +1,477 @@
+/*
+ * hdhomerun_debug.c
+ *
+ * Copyright © 2006-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.
+ */
+
+/*
+ * The debug logging includes optional support for connecting to the
+ * Silicondust support server. This option should not be used without
+ * being explicitly enabled by the user. Debug information should be
+ * limited to information useful to diagnosing a problem.
+ * - Silicondust.
+ */
+
+#include "hdhomerun.h"
+
+#if !defined(HDHOMERUN_DEBUG_HOST)
+#define HDHOMERUN_DEBUG_HOST "debug.silicondust.com"
+#endif
+#if !defined(HDHOMERUN_DEBUG_PORT)
+#define HDHOMERUN_DEBUG_PORT 8002
+#endif
+
+#define HDHOMERUN_DEBUG_CONNECT_RETRY_TIME 30000
+#define HDHOMERUN_DEBUG_CONNECT_TIMEOUT 10000
+#define HDHOMERUN_DEBUG_SEND_TIMEOUT 10000
+
+struct hdhomerun_debug_message_t
+{
+ struct hdhomerun_debug_message_t *next;
+ struct hdhomerun_debug_message_t *prev;
+ char buffer[2048];
+};
+
+struct hdhomerun_debug_t
+{
+ pthread_t thread;
+ volatile bool_t enabled;
+ volatile bool_t terminate;
+ char *prefix;
+
+ pthread_mutex_t print_lock;
+ pthread_mutex_t queue_lock;
+ pthread_mutex_t send_lock;
+
+ struct hdhomerun_debug_message_t *queue_head;
+ struct hdhomerun_debug_message_t *queue_tail;
+ uint32_t queue_depth;
+
+ uint64_t connect_delay;
+
+ char *file_name;
+ FILE *file_fp;
+ hdhomerun_sock_t sock;
+};
+
+static THREAD_FUNC_PREFIX hdhomerun_debug_thread_execute(void *arg);
+
+struct hdhomerun_debug_t *hdhomerun_debug_create(void)
+{
+ struct hdhomerun_debug_t *dbg = (struct hdhomerun_debug_t *)calloc(1, sizeof(struct hdhomerun_debug_t));
+ if (!dbg) {
+ return NULL;
+ }
+
+ dbg->sock = HDHOMERUN_SOCK_INVALID;
+
+ pthread_mutex_init(&dbg->print_lock, NULL);
+ pthread_mutex_init(&dbg->queue_lock, NULL);
+ pthread_mutex_init(&dbg->send_lock, NULL);
+
+ if (pthread_create(&dbg->thread, NULL, &hdhomerun_debug_thread_execute, dbg) != 0) {
+ free(dbg);
+ return NULL;
+ }
+
+ return dbg;
+}
+
+void hdhomerun_debug_destroy(struct hdhomerun_debug_t *dbg)
+{
+ if (!dbg) {
+ return;
+ }
+
+ dbg->terminate = TRUE;
+ pthread_join(dbg->thread, NULL);
+
+ if (dbg->prefix) {
+ free(dbg->prefix);
+ }
+ if (dbg->file_name) {
+ free(dbg->file_name);
+ }
+ if (dbg->file_fp) {
+ fclose(dbg->file_fp);
+ }
+ if (dbg->sock != HDHOMERUN_SOCK_INVALID) {
+ hdhomerun_sock_destroy(dbg->sock);
+ }
+
+ free(dbg);
+}
+
+/* Send lock held by caller */
+static void hdhomerun_debug_close_internal(struct hdhomerun_debug_t *dbg)
+{
+ if (dbg->file_fp) {
+ fclose(dbg->file_fp);
+ dbg->file_fp = NULL;
+ }
+
+ if (dbg->sock != HDHOMERUN_SOCK_INVALID) {
+ hdhomerun_sock_destroy(dbg->sock);
+ dbg->sock = HDHOMERUN_SOCK_INVALID;
+ }
+}
+
+void hdhomerun_debug_close(struct hdhomerun_debug_t *dbg, uint64_t timeout)
+{
+ if (!dbg) {
+ return;
+ }
+
+ if (timeout > 0) {
+ hdhomerun_debug_flush(dbg, timeout);
+ }
+
+ pthread_mutex_lock(&dbg->send_lock);
+ hdhomerun_debug_close_internal(dbg);
+ dbg->connect_delay = 0;
+ pthread_mutex_unlock(&dbg->send_lock);
+}
+
+void hdhomerun_debug_set_filename(struct hdhomerun_debug_t *dbg, const char *filename)
+{
+ if (!dbg) {
+ return;
+ }
+
+ pthread_mutex_lock(&dbg->send_lock);
+
+ if (!filename && !dbg->file_name) {
+ pthread_mutex_unlock(&dbg->send_lock);
+ return;
+ }
+ if (filename && dbg->file_name) {
+ if (strcmp(filename, dbg->file_name) == 0) {
+ pthread_mutex_unlock(&dbg->send_lock);
+ return;
+ }
+ }
+
+ hdhomerun_debug_close_internal(dbg);
+ dbg->connect_delay = 0;
+
+ if (dbg->file_name) {
+ free(dbg->file_name);
+ dbg->file_name = NULL;
+ }
+ if (filename) {
+ dbg->file_name = strdup(filename);
+ }
+
+ pthread_mutex_unlock(&dbg->send_lock);
+}
+
+void hdhomerun_debug_set_prefix(struct hdhomerun_debug_t *dbg, const char *prefix)
+{
+ if (!dbg) {
+ return;
+ }
+
+ pthread_mutex_lock(&dbg->print_lock);
+
+ if (dbg->prefix) {
+ free(dbg->prefix);
+ dbg->prefix = NULL;
+ }
+
+ if (prefix) {
+ dbg->prefix = strdup(prefix);
+ }
+
+ pthread_mutex_unlock(&dbg->print_lock);
+}
+
+void hdhomerun_debug_enable(struct hdhomerun_debug_t *dbg)
+{
+ if (!dbg) {
+ return;
+ }
+
+ dbg->enabled = TRUE;
+}
+
+void hdhomerun_debug_disable(struct hdhomerun_debug_t *dbg)
+{
+ if (!dbg) {
+ return;
+ }
+
+ dbg->enabled = FALSE;
+}
+
+bool_t hdhomerun_debug_enabled(struct hdhomerun_debug_t *dbg)
+{
+ if (!dbg) {
+ return FALSE;
+ }
+
+ return dbg->enabled;
+}
+
+void hdhomerun_debug_flush(struct hdhomerun_debug_t *dbg, uint64_t timeout)
+{
+ if (!dbg) {
+ return;
+ }
+
+ timeout = getcurrenttime() + timeout;
+
+ while (getcurrenttime() < timeout) {
+ pthread_mutex_lock(&dbg->queue_lock);
+ struct hdhomerun_debug_message_t *message = dbg->queue_tail;
+ pthread_mutex_unlock(&dbg->queue_lock);
+
+ if (!message) {
+ return;
+ }
+
+ msleep_approx(10);
+ }
+}
+
+void hdhomerun_debug_printf(struct hdhomerun_debug_t *dbg, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ hdhomerun_debug_vprintf(dbg, fmt, args);
+ va_end(args);
+}
+
+void hdhomerun_debug_vprintf(struct hdhomerun_debug_t *dbg, const char *fmt, va_list args)
+{
+ if (!dbg) {
+ return;
+ }
+ if (!dbg->enabled) {
+ return;
+ }
+
+ struct hdhomerun_debug_message_t *message = (struct hdhomerun_debug_message_t *)malloc(sizeof(struct hdhomerun_debug_message_t));
+ if (!message) {
+ return;
+ }
+
+ char *ptr = message->buffer;
+ char *end = message->buffer + sizeof(message->buffer) - 2;
+ *end = 0;
+
+ /*
+ * Timestamp.
+ */
+ time_t current_time = time(NULL);
+ ptr += strftime(ptr, end - ptr, "%Y%m%d-%H:%M:%S ", localtime(¤t_time));
+ if (ptr > end) {
+ ptr = end;
+ }
+
+ /*
+ * Debug prefix.
+ */
+ pthread_mutex_lock(&dbg->print_lock);
+
+ if (dbg->prefix) {
+ int len = snprintf(ptr, end - ptr, "%s ", dbg->prefix);
+ len = (len <= 0) ? 0 : len;
+ ptr += len;
+ if (ptr > end) {
+ ptr = end;
+ }
+ }
+
+ pthread_mutex_unlock(&dbg->print_lock);
+
+ /*
+ * Message text.
+ */
+ int len = vsnprintf(ptr, end - ptr, fmt, args);
+ len = (len < 0) ? 0 : len; /* len does not include null */
+ ptr += len;
+ if (ptr > end) {
+ ptr = end;
+ }
+
+ /*
+ * Force newline.
+ */
+ if ((ptr[-1] != '\n') && (ptr + 1 <= end)) {
+ *ptr++ = '\n';
+ }
+
+ /*
+ * Force NULL.
+ */
+ if (ptr + 1 > end) {
+ ptr = end - 1;
+ }
+ *ptr++ = 0;
+
+ /*
+ * Enqueue.
+ */
+ pthread_mutex_lock(&dbg->queue_lock);
+
+ message->prev = NULL;
+ message->next = dbg->queue_head;
+ dbg->queue_head = message;
+ if (message->next) {
+ message->next->prev = message;
+ } else {
+ dbg->queue_tail = message;
+ }
+ dbg->queue_depth++;
+
+ pthread_mutex_unlock(&dbg->queue_lock);
+}
+
+/* Send lock held by caller */
+static bool_t hdhomerun_debug_output_message_file(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message)
+{
+ if (!dbg->file_fp) {
+ uint64_t current_time = getcurrenttime();
+ if (current_time < dbg->connect_delay) {
+ return FALSE;
+ }
+ dbg->connect_delay = current_time + 30*1000;
+
+ dbg->file_fp = fopen(dbg->file_name, "a");
+ if (!dbg->file_fp) {
+ return FALSE;
+ }
+ }
+
+ fprintf(dbg->file_fp, "%s", message->buffer);
+ fflush(dbg->file_fp);
+
+ return TRUE;
+}
+
+/* Send lock held by caller */
+static bool_t hdhomerun_debug_output_message_sock(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message)
+{
+ if (dbg->sock == HDHOMERUN_SOCK_INVALID) {
+ uint64_t current_time = getcurrenttime();
+ if (current_time < dbg->connect_delay) {
+ return FALSE;
+ }
+ dbg->connect_delay = current_time + HDHOMERUN_DEBUG_CONNECT_RETRY_TIME;
+
+ dbg->sock = hdhomerun_sock_create_tcp();
+ if (dbg->sock == HDHOMERUN_SOCK_INVALID) {
+ return FALSE;
+ }
+
+ uint32_t remote_addr = hdhomerun_sock_getaddrinfo_addr(dbg->sock, HDHOMERUN_DEBUG_HOST);
+ if (remote_addr == 0) {
+ hdhomerun_debug_close_internal(dbg);
+ return FALSE;
+ }
+
+ if (!hdhomerun_sock_connect(dbg->sock, remote_addr, HDHOMERUN_DEBUG_PORT, HDHOMERUN_DEBUG_CONNECT_TIMEOUT)) {
+ hdhomerun_debug_close_internal(dbg);
+ return FALSE;
+ }
+ }
+
+ size_t length = strlen(message->buffer);
+ if (!hdhomerun_sock_send(dbg->sock, message->buffer, length, HDHOMERUN_DEBUG_SEND_TIMEOUT)) {
+ hdhomerun_debug_close_internal(dbg);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool_t hdhomerun_debug_output_message(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message)
+{
+ pthread_mutex_lock(&dbg->send_lock);
+
+ bool_t ret;
+ if (dbg->file_name) {
+ ret = hdhomerun_debug_output_message_file(dbg, message);
+ } else {
+ ret = hdhomerun_debug_output_message_sock(dbg, message);
+ }
+
+ pthread_mutex_unlock(&dbg->send_lock);
+ return ret;
+}
+
+static void hdhomerun_debug_pop_and_free_message(struct hdhomerun_debug_t *dbg)
+{
+ pthread_mutex_lock(&dbg->queue_lock);
+
+ struct hdhomerun_debug_message_t *message = dbg->queue_tail;
+ dbg->queue_tail = message->prev;
+ if (message->prev) {
+ message->prev->next = NULL;
+ } else {
+ dbg->queue_head = NULL;
+ }
+ dbg->queue_depth--;
+
+ pthread_mutex_unlock(&dbg->queue_lock);
+
+ free(message);
+}
+
+static THREAD_FUNC_PREFIX hdhomerun_debug_thread_execute(void *arg)
+{
+ struct hdhomerun_debug_t *dbg = (struct hdhomerun_debug_t *)arg;
+
+ while (!dbg->terminate) {
+
+ pthread_mutex_lock(&dbg->queue_lock);
+ struct hdhomerun_debug_message_t *message = dbg->queue_tail;
+ uint32_t queue_depth = dbg->queue_depth;
+ pthread_mutex_unlock(&dbg->queue_lock);
+
+ if (!message) {
+ msleep_approx(250);
+ continue;
+ }
+
+ if (queue_depth > 1024) {
+ hdhomerun_debug_pop_and_free_message(dbg);
+ continue;
+ }
+
+ if (!hdhomerun_debug_output_message(dbg, message)) {
+ msleep_approx(250);
+ continue;
+ }
+
+ hdhomerun_debug_pop_and_free_message(dbg);
+ }
+
+ return 0;
+}
diff --git a/libhdhomerun/hdhomerun_debug.h b/libhdhomerun/hdhomerun_debug.h
new file mode 100755
index 0000000..c7831ea
--- /dev/null
+++ b/libhdhomerun/hdhomerun_debug.h
@@ -0,0 +1,64 @@
+/*
+ * hdhomerun_debug.h
+ *
+ * Copyright © 2006 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.
+ */
+
+/*
+ * The debug logging includes optional support for connecting to the
+ * Silicondust support server. This option should not be used without
+ * being explicitly enabled by the user. Debug information should be
+ * limited to information useful to diagnosing a problem.
+ * - Silicondust.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct hdhomerun_debug_t;
+
+extern LIBTYPE struct hdhomerun_debug_t *hdhomerun_debug_create(void);
+extern LIBTYPE void hdhomerun_debug_destroy(struct hdhomerun_debug_t *dbg);
+
+extern LIBTYPE void hdhomerun_debug_set_prefix(struct hdhomerun_debug_t *dbg, const char *prefix);
+extern LIBTYPE void hdhomerun_debug_set_filename(struct hdhomerun_debug_t *dbg, const char *filename);
+extern LIBTYPE void hdhomerun_debug_enable(struct hdhomerun_debug_t *dbg);
+extern LIBTYPE void hdhomerun_debug_disable(struct hdhomerun_debug_t *dbg);
+extern LIBTYPE bool_t hdhomerun_debug_enabled(struct hdhomerun_debug_t *dbg);
+
+extern LIBTYPE void hdhomerun_debug_flush(struct hdhomerun_debug_t *dbg, uint64_t timeout);
+extern LIBTYPE void hdhomerun_debug_close(struct hdhomerun_debug_t *dbg, uint64_t timeout);
+
+extern LIBTYPE void hdhomerun_debug_printf(struct hdhomerun_debug_t *dbg, const char *fmt, ...);
+extern LIBTYPE void hdhomerun_debug_vprintf(struct hdhomerun_debug_t *dbg, const char *fmt, va_list args);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_device.c b/libhdhomerun/hdhomerun_device.c
new file mode 100755
index 0000000..059c043
--- /dev/null
+++ b/libhdhomerun/hdhomerun_device.c
@@ -0,0 +1,1407 @@
+/*
+ * hdhomerun_device.c
+ *
+ * Copyright © 2006-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_device_t {
+ struct hdhomerun_control_sock_t *cs;
+ struct hdhomerun_video_sock_t *vs;
+ struct hdhomerun_debug_t *dbg;
+ struct hdhomerun_channelscan_t *scan;
+ uint32_t multicast_ip;
+ uint16_t multicast_port;
+ uint32_t device_id;
+ unsigned int tuner;
+ uint32_t lockkey;
+ char name[32];
+ char model[32];
+};
+
+static int hdhomerun_device_set_device_normal(struct hdhomerun_device_t *hd, uint32_t device_id, uint32_t device_ip)
+{
+ if (!hd->cs) {
+ hd->cs = hdhomerun_control_create(0, 0, hd->dbg);
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: failed to create control object\n");
+ return -1;
+ }
+ }
+
+ hdhomerun_control_set_device(hd->cs, device_id, device_ip);
+
+ if ((device_id == 0) || (device_id == HDHOMERUN_DEVICE_ID_WILDCARD)) {
+ device_id = hdhomerun_control_get_device_id(hd->cs);
+ }
+
+ hd->multicast_ip = 0;
+ hd->multicast_port = 0;
+ hd->device_id = device_id;
+ hd->tuner = 0;
+ hd->lockkey = 0;
+
+ sprintf(hd->name, "%08lX-%u", (unsigned long)hd->device_id, hd->tuner);
+ hd->model[0] = 0; /* clear cached model string */
+
+ return 1;
+}
+
+static int hdhomerun_device_set_device_multicast(struct hdhomerun_device_t *hd, uint32_t multicast_ip)
+{
+ if (hd->cs) {
+ hdhomerun_control_destroy(hd->cs);
+ hd->cs = NULL;
+ }
+
+ hd->multicast_ip = multicast_ip;
+ hd->multicast_port = 0;
+ hd->device_id = 0;
+ hd->tuner = 0;
+ hd->lockkey = 0;
+
+ unsigned long ip = multicast_ip;
+ sprintf(hd->name, "%lu.%lu.%lu.%lu", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, (ip >> 0) & 0xFF);
+ sprintf(hd->model, "multicast");
+
+ return 1;
+}
+
+int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_id, uint32_t device_ip)
+{
+ if ((device_id == 0) && (device_ip == 0)) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: device not specified\n");
+ return -1;
+ }
+
+ if (hdhomerun_discover_is_ip_multicast(device_ip)) {
+ return hdhomerun_device_set_device_multicast(hd, device_ip);
+ }
+
+ return hdhomerun_device_set_device_normal(hd, device_id, device_ip);
+}
+
+int hdhomerun_device_set_tuner(struct hdhomerun_device_t *hd, unsigned int tuner)
+{
+ if (hd->multicast_ip != 0) {
+ if (tuner != 0) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner: tuner cannot be specified in multicast mode\n");
+ return -1;
+ }
+
+ return 1;
+ }
+
+ hd->tuner = tuner;
+ sprintf(hd->name, "%08lX-%u", (unsigned long)hd->device_id, hd->tuner);
+
+ return 1;
+}
+
+struct hdhomerun_device_t *hdhomerun_device_create(uint32_t device_id, uint32_t device_ip, unsigned int tuner, struct hdhomerun_debug_t *dbg)
+{
+ struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)calloc(1, sizeof(struct hdhomerun_device_t));
+ if (!hd) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_device_create: failed to allocate device object\n");
+ return NULL;
+ }
+
+ hd->dbg = dbg;
+
+ if ((device_id == 0) && (device_ip == 0) && (tuner == 0)) {
+ return hd;
+ }
+
+ if (hdhomerun_device_set_device(hd, device_id, device_ip) <= 0) {
+ free(hd);
+ return NULL;
+ }
+ if (hdhomerun_device_set_tuner(hd, tuner) <= 0) {
+ free(hd);
+ return NULL;
+ }
+
+ return hd;
+}
+
+void hdhomerun_device_destroy(struct hdhomerun_device_t *hd)
+{
+ if (hd->scan) {
+ channelscan_destroy(hd->scan);
+ }
+
+ if (hd->vs) {
+ hdhomerun_video_destroy(hd->vs);
+ }
+
+ if (hd->cs) {
+ hdhomerun_control_destroy(hd->cs);
+ }
+
+ free(hd);
+}
+
+static bool_t is_hex_char(char c)
+{
+ if ((c >= '0') && (c <= '9')) {
+ return TRUE;
+ }
+ if ((c >= 'A') && (c <= 'F')) {
+ return TRUE;
+ }
+ if ((c >= 'a') && (c <= 'f')) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static struct hdhomerun_device_t *hdhomerun_device_create_from_str_device_id(const char *device_str, struct hdhomerun_debug_t *dbg)
+{
+ int i;
+ const char *ptr = device_str;
+ for (i = 0; i < 8; i++) {
+ if (!is_hex_char(*ptr++)) {
+ return NULL;
+ }
+ }
+
+ if (*ptr == 0) {
+ unsigned long device_id;
+ if (sscanf(device_str, "%lx", &device_id) != 1) {
+ return NULL;
+ }
+ return hdhomerun_device_create((uint32_t)device_id, 0, 0, dbg);
+ }
+
+ if (*ptr == '-') {
+ unsigned long device_id;
+ unsigned int tuner;
+ if (sscanf(device_str, "%lx-%u", &device_id, &tuner) != 2) {
+ return NULL;
+ }
+ return hdhomerun_device_create((uint32_t)device_id, 0, tuner, dbg);
+ }
+
+ return NULL;
+}
+
+static struct hdhomerun_device_t *hdhomerun_device_create_from_str_ip_result(unsigned long a[4], unsigned int port, unsigned int tuner, struct hdhomerun_debug_t *dbg)
+{
+ unsigned long device_ip = (a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0);
+ struct hdhomerun_device_t *hd = hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, (uint32_t)device_ip, tuner, dbg);
+ if (!hd) {
+ return NULL;
+ }
+
+ if (hd->multicast_ip != 0) {
+ hd->multicast_port = (uint16_t)port;
+ }
+
+ return hd;
+}
+
+static struct hdhomerun_device_t *hdhomerun_device_create_from_str_ip(const char *device_str, struct hdhomerun_debug_t *dbg)
+{
+ unsigned long a[4];
+ unsigned int port = 0;
+ unsigned int tuner = 0;
+
+ if (sscanf(device_str, "%lu.%lu.%lu.%lu:%u", &a[0], &a[1], &a[2], &a[3], &port) == 5) {
+ return hdhomerun_device_create_from_str_ip_result(a, port, tuner, dbg);
+ }
+ if (sscanf(device_str, "%lu.%lu.%lu.%lu-%u", &a[0], &a[1], &a[2], &a[3], &tuner) == 5) {
+ return hdhomerun_device_create_from_str_ip_result(a, port, tuner, dbg);
+ }
+ if (sscanf(device_str, "%lu.%lu.%lu.%lu", &a[0], &a[1], &a[2], &a[3]) == 4) {
+ return hdhomerun_device_create_from_str_ip_result(a, port, tuner, dbg);
+ }
+
+ return NULL;
+}
+
+static struct hdhomerun_device_t *hdhomerun_device_create_from_str_dns(const char *device_str, struct hdhomerun_debug_t *dbg)
+{
+#if defined(__CYGWIN__)
+ return NULL;
+#else
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ struct addrinfo *sock_info;
+ if (getaddrinfo(device_str, "65001", &hints, &sock_info) != 0) {
+ return NULL;
+ }
+
+ struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr;
+ uint32_t device_ip = ntohl(sock_addr->sin_addr.s_addr);
+ freeaddrinfo(sock_info);
+
+ if (device_ip == 0) {
+ return NULL;
+ }
+
+ return hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, (uint32_t)device_ip, 0, dbg);
+#endif
+}
+
+struct hdhomerun_device_t *hdhomerun_device_create_from_str(const char *device_str, struct hdhomerun_debug_t *dbg)
+{
+ struct hdhomerun_device_t *device = hdhomerun_device_create_from_str_device_id(device_str, dbg);
+ if (device) {
+ return device;
+ }
+
+ device = hdhomerun_device_create_from_str_ip(device_str, dbg);
+ if (device) {
+ return device;
+ }
+
+ device = hdhomerun_device_create_from_str_dns(device_str, dbg);
+ if (device) {
+ return device;
+ }
+
+ return NULL;
+}
+
+int hdhomerun_device_set_tuner_from_str(struct hdhomerun_device_t *hd, const char *tuner_str)
+{
+ unsigned int tuner;
+ if (sscanf(tuner_str, "%u", &tuner) == 1) {
+ hdhomerun_device_set_tuner(hd, tuner);
+ return 1;
+ }
+ if (sscanf(tuner_str, "/tuner%u", &tuner) == 1) {
+ hdhomerun_device_set_tuner(hd, tuner);
+ return 1;
+ }
+
+ return -1;
+}
+
+const char *hdhomerun_device_get_name(struct hdhomerun_device_t *hd)
+{
+ return hd->name;
+}
+
+uint32_t hdhomerun_device_get_device_id(struct hdhomerun_device_t *hd)
+{
+ return hd->device_id;
+}
+
+uint32_t hdhomerun_device_get_device_ip(struct hdhomerun_device_t *hd)
+{
+ if (hd->multicast_ip != 0) {
+ return hd->multicast_ip;
+ }
+ if (hd->cs) {
+ return hdhomerun_control_get_device_ip(hd->cs);
+ }
+
+ return 0;
+}
+
+uint32_t hdhomerun_device_get_device_id_requested(struct hdhomerun_device_t *hd)
+{
+ if (hd->multicast_ip != 0) {
+ return 0;
+ }
+ if (hd->cs) {
+ return hdhomerun_control_get_device_id_requested(hd->cs);
+ }
+
+ return 0;
+}
+
+uint32_t hdhomerun_device_get_device_ip_requested(struct hdhomerun_device_t *hd)
+{
+ if (hd->multicast_ip != 0) {
+ return hd->multicast_ip;
+ }
+ if (hd->cs) {
+ return hdhomerun_control_get_device_ip_requested(hd->cs);
+ }
+
+ return 0;
+}
+
+unsigned int hdhomerun_device_get_tuner(struct hdhomerun_device_t *hd)
+{
+ return hd->tuner;
+}
+
+struct hdhomerun_control_sock_t *hdhomerun_device_get_control_sock(struct hdhomerun_device_t *hd)
+{
+ return hd->cs;
+}
+
+struct hdhomerun_video_sock_t *hdhomerun_device_get_video_sock(struct hdhomerun_device_t *hd)
+{
+ if (hd->vs) {
+ return hd->vs;
+ }
+
+ bool_t allow_port_reuse = (hd->multicast_port != 0);
+
+ hd->vs = hdhomerun_video_create(hd->multicast_port, allow_port_reuse, VIDEO_DATA_BUFFER_SIZE_1S * 2, hd->dbg);
+ if (!hd->vs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_video_sock: failed to create video object\n");
+ return NULL;
+ }
+
+ return hd->vs;
+}
+
+uint32_t hdhomerun_device_get_local_machine_addr(struct hdhomerun_device_t *hd)
+{
+ if (hd->cs) {
+ return hdhomerun_control_get_local_addr(hd->cs);
+ }
+
+ return 0;
+}
+
+static uint32_t hdhomerun_device_get_status_parse(const char *status_str, const char *tag)
+{
+ const char *ptr = strstr(status_str, tag);
+ if (!ptr) {
+ return 0;
+ }
+
+ unsigned long value = 0;
+ sscanf(ptr + strlen(tag), "%lu", &value);
+
+ return (uint32_t)value;
+}
+
+static bool_t hdhomerun_device_get_tuner_status_lock_is_bcast(struct hdhomerun_tuner_status_t *status)
+{
+ if (strcmp(status->lock_str, "8vsb") == 0) {
+ return TRUE;
+ }
+ if (strncmp(status->lock_str, "t8", 2) == 0) {
+ return TRUE;
+ }
+ if (strncmp(status->lock_str, "t7", 2) == 0) {
+ return TRUE;
+ }
+ if (strncmp(status->lock_str, "t6", 2) == 0) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+uint32_t hdhomerun_device_get_tuner_status_ss_color(struct hdhomerun_tuner_status_t *status)
+{
+ unsigned int ss_yellow_min;
+ unsigned int ss_green_min;
+
+ if (!status->lock_supported) {
+ return HDHOMERUN_STATUS_COLOR_NEUTRAL;
+ }
+
+ if (hdhomerun_device_get_tuner_status_lock_is_bcast(status)) {
+ ss_yellow_min = 50; /* -30dBmV */
+ ss_green_min = 75; /* -15dBmV */
+ } else {
+ ss_yellow_min = 80; /* -12dBmV */
+ ss_green_min = 90; /* -6dBmV */
+ }
+
+ if (status->signal_strength >= ss_green_min) {
+ return HDHOMERUN_STATUS_COLOR_GREEN;
+ }
+ if (status->signal_strength >= ss_yellow_min) {
+ return HDHOMERUN_STATUS_COLOR_YELLOW;
+ }
+
+ return HDHOMERUN_STATUS_COLOR_RED;
+}
+
+uint32_t hdhomerun_device_get_tuner_status_snq_color(struct hdhomerun_tuner_status_t *status)
+{
+ if (status->signal_to_noise_quality >= 70) {
+ return HDHOMERUN_STATUS_COLOR_GREEN;
+ }
+ if (status->signal_to_noise_quality >= 50) {
+ return HDHOMERUN_STATUS_COLOR_YELLOW;
+ }
+
+ return HDHOMERUN_STATUS_COLOR_RED;
+}
+
+uint32_t hdhomerun_device_get_tuner_status_seq_color(struct hdhomerun_tuner_status_t *status)
+{
+ if (status->symbol_error_quality >= 100) {
+ return HDHOMERUN_STATUS_COLOR_GREEN;
+ }
+
+ return HDHOMERUN_STATUS_COLOR_RED;
+}
+
+int hdhomerun_device_get_tuner_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_status: device not set\n");
+ return -1;
+ }
+
+ memset(status, 0, sizeof(struct hdhomerun_tuner_status_t));
+
+ char name[32];
+ sprintf(name, "/tuner%u/status", hd->tuner);
+
+ char *status_str;
+ int ret = hdhomerun_control_get(hd->cs, name, &status_str, NULL);
+ if (ret <= 0) {
+ return ret;
+ }
+
+ if (pstatus_str) {
+ *pstatus_str = status_str;
+ }
+
+ if (status) {
+ char *channel = strstr(status_str, "ch=");
+ if (channel) {
+ sscanf(channel + 3, "%31s", status->channel);
+ }
+
+ char *lock = strstr(status_str, "lock=");
+ if (lock) {
+ sscanf(lock + 5, "%31s", status->lock_str);
+ }
+
+ status->signal_strength = (unsigned int)hdhomerun_device_get_status_parse(status_str, "ss=");
+ status->signal_to_noise_quality = (unsigned int)hdhomerun_device_get_status_parse(status_str, "snq=");
+ status->symbol_error_quality = (unsigned int)hdhomerun_device_get_status_parse(status_str, "seq=");
+ status->raw_bits_per_second = hdhomerun_device_get_status_parse(status_str, "bps=");
+ status->packets_per_second = hdhomerun_device_get_status_parse(status_str, "pps=");
+
+ status->signal_present = status->signal_strength >= 45;
+
+ if (strcmp(status->lock_str, "none") != 0) {
+ if (status->lock_str[0] == '(') {
+ status->lock_unsupported = TRUE;
+ } else {
+ status->lock_supported = TRUE;
+ }
+ }
+ }
+
+ return 1;
+}
+
+int hdhomerun_device_get_oob_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_oob_status: device not set\n");
+ return -1;
+ }
+
+ memset(status, 0, sizeof(struct hdhomerun_tuner_status_t));
+
+ char *status_str;
+ int ret = hdhomerun_control_get(hd->cs, "/oob/status", &status_str, NULL);
+ if (ret <= 0) {
+ return ret;
+ }
+
+ if (pstatus_str) {
+ *pstatus_str = status_str;
+ }
+
+ if (status) {
+ char *channel = strstr(status_str, "ch=");
+ if (channel) {
+ sscanf(channel + 3, "%31s", status->channel);
+ }
+
+ char *lock = strstr(status_str, "lock=");
+ if (lock) {
+ sscanf(lock + 5, "%31s", status->lock_str);
+ }
+
+ status->signal_strength = (unsigned int)hdhomerun_device_get_status_parse(status_str, "ss=");
+ status->signal_to_noise_quality = (unsigned int)hdhomerun_device_get_status_parse(status_str, "snq=");
+ status->signal_present = status->signal_strength >= 45;
+ status->lock_supported = (strcmp(status->lock_str, "none") != 0);
+ }
+
+ return 1;
+}
+
+int hdhomerun_device_get_tuner_vstatus(struct hdhomerun_device_t *hd, char **pvstatus_str, struct hdhomerun_tuner_vstatus_t *vstatus)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_vstatus: device not set\n");
+ return -1;
+ }
+
+ memset(vstatus, 0, sizeof(struct hdhomerun_tuner_vstatus_t));
+
+ char var_name[32];
+ sprintf(var_name, "/tuner%u/vstatus", hd->tuner);
+
+ char *vstatus_str;
+ int ret = hdhomerun_control_get(hd->cs, var_name, &vstatus_str, NULL);
+ if (ret <= 0) {
+ return ret;
+ }
+
+ if (pvstatus_str) {
+ *pvstatus_str = vstatus_str;
+ }
+
+ if (vstatus) {
+ char *vch = strstr(vstatus_str, "vch=");
+ if (vch) {
+ sscanf(vch + 4, "%31s", vstatus->vchannel);
+ }
+
+ char *name = strstr(vstatus_str, "name=");
+ if (name) {
+ sscanf(name + 5, "%31s", vstatus->name);
+ }
+
+ char *auth = strstr(vstatus_str, "auth=");
+ if (auth) {
+ sscanf(auth + 5, "%31s", vstatus->auth);
+ }
+
+ char *cci = strstr(vstatus_str, "cci=");
+ if (cci) {
+ sscanf(cci + 4, "%31s", vstatus->cci);
+ }
+
+ char *cgms = strstr(vstatus_str, "cgms=");
+ if (cgms) {
+ sscanf(cgms + 5, "%31s", vstatus->cgms);
+ }
+
+ if (strncmp(vstatus->auth, "not-subscribed", 14) == 0) {
+ vstatus->not_subscribed = TRUE;
+ }
+
+ if (strncmp(vstatus->auth, "error", 5) == 0) {
+ vstatus->not_available = TRUE;
+ }
+ if (strncmp(vstatus->auth, "dialog", 6) == 0) {
+ vstatus->not_available = TRUE;
+ }
+
+ if (strncmp(vstatus->cci, "protected", 9) == 0) {
+ vstatus->copy_protected = TRUE;
+ }
+ if (strncmp(vstatus->cgms, "protected", 9) == 0) {
+ vstatus->copy_protected = TRUE;
+ }
+ }
+
+ return 1;
+}
+
+int hdhomerun_device_get_tuner_streaminfo(struct hdhomerun_device_t *hd, char **pstreaminfo)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_streaminfo: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/streaminfo", hd->tuner);
+ return hdhomerun_control_get(hd->cs, name, pstreaminfo, NULL);
+}
+
+int hdhomerun_device_get_tuner_channel(struct hdhomerun_device_t *hd, char **pchannel)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_channel: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/channel", hd->tuner);
+ return hdhomerun_control_get(hd->cs, name, pchannel, NULL);
+}
+
+int hdhomerun_device_get_tuner_vchannel(struct hdhomerun_device_t *hd, char **pvchannel)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_vchannel: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/vchannel", hd->tuner);
+ return hdhomerun_control_get(hd->cs, name, pvchannel, NULL);
+}
+
+int hdhomerun_device_get_tuner_channelmap(struct hdhomerun_device_t *hd, char **pchannelmap)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_channelmap: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/channelmap", hd->tuner);
+ return hdhomerun_control_get(hd->cs, name, pchannelmap, NULL);
+}
+
+int hdhomerun_device_get_tuner_filter(struct hdhomerun_device_t *hd, char **pfilter)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_filter: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/filter", hd->tuner);
+ return hdhomerun_control_get(hd->cs, name, pfilter, NULL);
+}
+
+int hdhomerun_device_get_tuner_program(struct hdhomerun_device_t *hd, char **pprogram)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_program: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/program", hd->tuner);
+ return hdhomerun_control_get(hd->cs, name, pprogram, NULL);
+}
+
+int hdhomerun_device_get_tuner_target(struct hdhomerun_device_t *hd, char **ptarget)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_target: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/target", hd->tuner);
+ return hdhomerun_control_get(hd->cs, name, ptarget, NULL);
+}
+
+static int hdhomerun_device_get_tuner_plotsample_internal(struct hdhomerun_device_t *hd, const char *name, struct hdhomerun_plotsample_t **psamples, size_t *pcount)
+{
+ char *result;
+ int ret = hdhomerun_control_get(hd->cs, name, &result, NULL);
+ if (ret <= 0) {
+ return ret;
+ }
+
+ struct hdhomerun_plotsample_t *samples = (struct hdhomerun_plotsample_t *)result;
+ *psamples = samples;
+ size_t count = 0;
+
+ while (1) {
+ char *ptr = strchr(result, ' ');
+ if (!ptr) {
+ break;
+ }
+ *ptr++ = 0;
+
+ unsigned long raw;
+ if (sscanf(result, "%lx", &raw) != 1) {
+ break;
+ }
+
+ uint16_t real = (raw >> 12) & 0x0FFF;
+ if (real & 0x0800) {
+ real |= 0xF000;
+ }
+
+ uint16_t imag = (raw >> 0) & 0x0FFF;
+ if (imag & 0x0800) {
+ imag |= 0xF000;
+ }
+
+ samples->real = (int16_t)real;
+ samples->imag = (int16_t)imag;
+ samples++;
+ count++;
+
+ result = ptr;
+ }
+
+ *pcount = count;
+ return 1;
+}
+
+int hdhomerun_device_get_tuner_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_plotsample: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/plotsample", hd->tuner);
+ return hdhomerun_device_get_tuner_plotsample_internal(hd, name, psamples, pcount);
+}
+
+int hdhomerun_device_get_oob_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_oob_plotsample: device not set\n");
+ return -1;
+ }
+
+ return hdhomerun_device_get_tuner_plotsample_internal(hd, "/oob/plotsample", psamples, pcount);
+}
+
+int hdhomerun_device_get_tuner_lockkey_owner(struct hdhomerun_device_t *hd, char **powner)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_lockkey_owner: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/lockkey", hd->tuner);
+ return hdhomerun_control_get(hd->cs, name, powner, NULL);
+}
+
+int hdhomerun_device_get_ir_target(struct hdhomerun_device_t *hd, char **ptarget)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_ir_target: device not set\n");
+ return -1;
+ }
+
+ return hdhomerun_control_get(hd->cs, "/ir/target", ptarget, NULL);
+}
+
+int hdhomerun_device_get_lineup_location(struct hdhomerun_device_t *hd, char **plocation)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_lineup_location: device not set\n");
+ return -1;
+ }
+
+ return hdhomerun_control_get(hd->cs, "/lineup/location", plocation, NULL);
+}
+
+int hdhomerun_device_get_version(struct hdhomerun_device_t *hd, char **pversion_str, uint32_t *pversion_num)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_version: device not set\n");
+ return -1;
+ }
+
+ char *version_str;
+ int ret = hdhomerun_control_get(hd->cs, "/sys/version", &version_str, NULL);
+ if (ret <= 0) {
+ return ret;
+ }
+
+ if (pversion_str) {
+ *pversion_str = version_str;
+ }
+
+ if (pversion_num) {
+ unsigned long version_num;
+ if (sscanf(version_str, "%lu", &version_num) != 1) {
+ *pversion_num = 0;
+ } else {
+ *pversion_num = (uint32_t)version_num;
+ }
+ }
+
+ return 1;
+}
+
+int hdhomerun_device_get_supported(struct hdhomerun_device_t *hd, char *prefix, char **pstr)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_channel: device not set\n");
+ return -1;
+ }
+
+ char *features;
+ int ret = hdhomerun_control_get(hd->cs, "/sys/features", &features, NULL);
+ if (ret <= 0) {
+ return ret;
+ }
+
+ if (!prefix) {
+ *pstr = features;
+ return 1;
+ }
+
+ char *ptr = strstr(features, prefix);
+ if (!ptr) {
+ return 0;
+ }
+
+ ptr += strlen(prefix);
+ *pstr = ptr;
+
+ ptr = strchr(ptr, '\n');
+ if (ptr) {
+ *ptr = 0;
+ }
+
+ return 1;
+}
+
+int hdhomerun_device_set_tuner_channel(struct hdhomerun_device_t *hd, const char *channel)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_channel: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/channel", hd->tuner);
+ return hdhomerun_control_set_with_lockkey(hd->cs, name, channel, hd->lockkey, NULL, NULL);
+}
+
+int hdhomerun_device_set_tuner_vchannel(struct hdhomerun_device_t *hd, const char *vchannel)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_vchannel: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/vchannel", hd->tuner);
+ return hdhomerun_control_set_with_lockkey(hd->cs, name, vchannel, hd->lockkey, NULL, NULL);
+}
+
+int hdhomerun_device_set_tuner_channelmap(struct hdhomerun_device_t *hd, const char *channelmap)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_channelmap: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/channelmap", hd->tuner);
+ return hdhomerun_control_set_with_lockkey(hd->cs, name, channelmap, hd->lockkey, NULL, NULL);
+}
+
+int hdhomerun_device_set_tuner_filter(struct hdhomerun_device_t *hd, const char *filter)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_filter: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/filter", hd->tuner);
+ return hdhomerun_control_set_with_lockkey(hd->cs, name, filter, hd->lockkey, NULL, NULL);
+}
+
+static int hdhomerun_device_set_tuner_filter_by_array_append(char **pptr, char *end, uint16_t range_begin, uint16_t range_end)
+{
+ char *ptr = *pptr;
+
+ size_t available = end - ptr;
+ size_t required;
+
+ if (range_begin == range_end) {
+ required = snprintf(ptr, available, "0x%04x ", range_begin) + 1;
+ } else {
+ required = snprintf(ptr, available, "0x%04x-0x%04x ", range_begin, range_end) + 1;
+ }
+
+ if (required > available) {
+ return FALSE;
+ }
+
+ *pptr = strchr(ptr, 0);
+ return TRUE;
+}
+
+int hdhomerun_device_set_tuner_filter_by_array(struct hdhomerun_device_t *hd, unsigned char filter_array[0x2000])
+{
+ char filter[1024];
+ char *ptr = filter;
+ char *end = filter + sizeof(filter);
+
+ uint16_t range_begin = 0xFFFF;
+ uint16_t range_end = 0xFFFF;
+
+ uint16_t i;
+ for (i = 0; i <= 0x1FFF; i++) {
+ if (!filter_array[i]) {
+ if (range_begin == 0xFFFF) {
+ continue;
+ }
+ if (!hdhomerun_device_set_tuner_filter_by_array_append(&ptr, end, range_begin, range_end)) {
+ return 0;
+ }
+ range_begin = 0xFFFF;
+ range_end = 0xFFFF;
+ continue;
+ }
+
+ if (range_begin == 0xFFFF) {
+ range_begin = i;
+ range_end = i;
+ continue;
+ }
+
+ range_end = i;
+ }
+
+ if (range_begin != 0xFFFF) {
+ if (!hdhomerun_device_set_tuner_filter_by_array_append(&ptr, end, range_begin, range_end)) {
+ return 0;
+ }
+ }
+
+ /* Remove trailing space. */
+ if (ptr > filter) {
+ ptr--;
+ }
+ *ptr = 0;
+
+ return hdhomerun_device_set_tuner_filter(hd, filter);
+}
+
+int hdhomerun_device_set_tuner_program(struct hdhomerun_device_t *hd, const char *program)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_program: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/program", hd->tuner);
+ return hdhomerun_control_set_with_lockkey(hd->cs, name, program, hd->lockkey, NULL, NULL);
+}
+
+int hdhomerun_device_set_tuner_target(struct hdhomerun_device_t *hd, const char *target)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_target: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/target", hd->tuner);
+ return hdhomerun_control_set_with_lockkey(hd->cs, name, target, hd->lockkey, NULL, NULL);
+}
+
+static int hdhomerun_device_set_tuner_target_to_local(struct hdhomerun_device_t *hd, const char *protocol)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_target_to_local: device not set\n");
+ return -1;
+ }
+ if (!hd->vs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_target_to_local: video not initialized\n");
+ return -1;
+ }
+
+ /* Set target. */
+ char target[64];
+ uint32_t local_ip = hdhomerun_control_get_local_addr(hd->cs);
+ uint16_t local_port = hdhomerun_video_get_local_port(hd->vs);
+ sprintf(target, "%s://%u.%u.%u.%u:%u",
+ protocol,
+ (unsigned int)(local_ip >> 24) & 0xFF, (unsigned int)(local_ip >> 16) & 0xFF,
+ (unsigned int)(local_ip >> 8) & 0xFF, (unsigned int)(local_ip >> 0) & 0xFF,
+ (unsigned int)local_port
+ );
+
+ return hdhomerun_device_set_tuner_target(hd, target);
+}
+
+int hdhomerun_device_set_ir_target(struct hdhomerun_device_t *hd, const char *target)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_ir_target: device not set\n");
+ return -1;
+ }
+
+ return hdhomerun_control_set(hd->cs, "/ir/target", target, NULL, NULL);
+}
+
+int hdhomerun_device_set_lineup_location(struct hdhomerun_device_t *hd, const char *location)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_lineup_location: device not set\n");
+ return -1;
+ }
+
+ return hdhomerun_control_set(hd->cs, "/lineup/location", location, NULL, NULL);
+}
+
+int hdhomerun_device_set_sys_dvbc_modulation(struct hdhomerun_device_t *hd, const char *modulation_list)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_sys_dvbc_modulation: device not set\n");
+ return -1;
+ }
+
+ return hdhomerun_control_set(hd->cs, "/sys/dvbc_modulation", modulation_list, NULL, NULL);
+}
+
+int hdhomerun_device_get_var(struct hdhomerun_device_t *hd, const char *name, char **pvalue, char **perror)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_var: device not set\n");
+ return -1;
+ }
+
+ return hdhomerun_control_get(hd->cs, name, pvalue, perror);
+}
+
+int hdhomerun_device_set_var(struct hdhomerun_device_t *hd, const char *name, const char *value, char **pvalue, char **perror)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_var: device not set\n");
+ return -1;
+ }
+
+ return hdhomerun_control_set_with_lockkey(hd->cs, name, value, hd->lockkey, pvalue, perror);
+}
+
+int hdhomerun_device_tuner_lockkey_request(struct hdhomerun_device_t *hd, char **perror)
+{
+ if (hd->multicast_ip != 0) {
+ return 1;
+ }
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_tuner_lockkey_request: device not set\n");
+ return -1;
+ }
+
+ uint32_t new_lockkey = random_get32();
+
+ char name[32];
+ sprintf(name, "/tuner%u/lockkey", hd->tuner);
+
+ char new_lockkey_str[64];
+ sprintf(new_lockkey_str, "%u", (unsigned int)new_lockkey);
+
+ int ret = hdhomerun_control_set_with_lockkey(hd->cs, name, new_lockkey_str, hd->lockkey, NULL, perror);
+ if (ret <= 0) {
+ hd->lockkey = 0;
+ return ret;
+ }
+
+ hd->lockkey = new_lockkey;
+ return ret;
+}
+
+int hdhomerun_device_tuner_lockkey_release(struct hdhomerun_device_t *hd)
+{
+ if (hd->multicast_ip != 0) {
+ return 1;
+ }
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_tuner_lockkey_release: device not set\n");
+ return -1;
+ }
+
+ if (hd->lockkey == 0) {
+ return 1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/lockkey", hd->tuner);
+ int ret = hdhomerun_control_set_with_lockkey(hd->cs, name, "none", hd->lockkey, NULL, NULL);
+
+ hd->lockkey = 0;
+ return ret;
+}
+
+int hdhomerun_device_tuner_lockkey_force(struct hdhomerun_device_t *hd)
+{
+ if (hd->multicast_ip != 0) {
+ return 1;
+ }
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_tuner_lockkey_force: device not set\n");
+ return -1;
+ }
+
+ char name[32];
+ sprintf(name, "/tuner%u/lockkey", hd->tuner);
+ int ret = hdhomerun_control_set(hd->cs, name, "force", NULL, NULL);
+
+ hd->lockkey = 0;
+ return ret;
+}
+
+void hdhomerun_device_tuner_lockkey_use_value(struct hdhomerun_device_t *hd, uint32_t lockkey)
+{
+ if (hd->multicast_ip != 0) {
+ return;
+ }
+
+ hd->lockkey = lockkey;
+}
+
+int hdhomerun_device_wait_for_lock(struct hdhomerun_device_t *hd, struct hdhomerun_tuner_status_t *status)
+{
+ /* Delay for SS reading to be valid (signal present). */
+ msleep_minimum(250);
+
+ /* Wait for up to 2.5 seconds for lock. */
+ uint64_t timeout = getcurrenttime() + 2500;
+ while (1) {
+ /* Get status to check for lock. Quality numbers will not be valid yet. */
+ int ret = hdhomerun_device_get_tuner_status(hd, NULL, status);
+ if (ret <= 0) {
+ return ret;
+ }
+
+ if (!status->signal_present) {
+ return 1;
+ }
+ if (status->lock_supported || status->lock_unsupported) {
+ return 1;
+ }
+
+ if (getcurrenttime() >= timeout) {
+ return 1;
+ }
+
+ msleep_approx(250);
+ }
+}
+
+int hdhomerun_device_stream_start(struct hdhomerun_device_t *hd)
+{
+ hdhomerun_device_get_video_sock(hd);
+ if (!hd->vs) {
+ return -1;
+ }
+
+ /* Set target. */
+ if (hd->multicast_ip != 0) {
+ int ret = hdhomerun_video_join_multicast_group(hd->vs, hd->multicast_ip, 0);
+ if (ret <= 0) {
+ return ret;
+ }
+ } else {
+ int ret = hdhomerun_device_set_tuner_target_to_local(hd, HDHOMERUN_TARGET_PROTOCOL_RTP);
+ if (ret == 0) {
+ ret = hdhomerun_device_set_tuner_target_to_local(hd, HDHOMERUN_TARGET_PROTOCOL_UDP);
+ }
+ if (ret <= 0) {
+ return ret;
+ }
+ }
+
+ /* Flush video buffer. */
+ msleep_minimum(64);
+ hdhomerun_video_flush(hd->vs);
+
+ /* Success. */
+ return 1;
+}
+
+uint8_t *hdhomerun_device_stream_recv(struct hdhomerun_device_t *hd, size_t max_size, size_t *pactual_size)
+{
+ if (!hd->vs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_stream_recv: video not initialized\n");
+ return NULL;
+ }
+
+ return hdhomerun_video_recv(hd->vs, max_size, pactual_size);
+}
+
+void hdhomerun_device_stream_flush(struct hdhomerun_device_t *hd)
+{
+ if (!hd->vs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_stream_flush: video not initialized\n");
+ return;
+ }
+
+ hdhomerun_video_flush(hd->vs);
+}
+
+void hdhomerun_device_stream_stop(struct hdhomerun_device_t *hd)
+{
+ if (!hd->vs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_stream_stop: video not initialized\n");
+ return;
+ }
+
+ if (hd->multicast_ip != 0) {
+ hdhomerun_video_leave_multicast_group(hd->vs, hd->multicast_ip, 0);
+ } else {
+ hdhomerun_device_set_tuner_target(hd, "none");
+ }
+}
+
+int hdhomerun_device_channelscan_init(struct hdhomerun_device_t *hd, const char *channelmap)
+{
+ if (hd->scan) {
+ channelscan_destroy(hd->scan);
+ }
+
+ hd->scan = channelscan_create(hd, channelmap);
+ if (!hd->scan) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_init: failed to create scan object\n");
+ return -1;
+ }
+
+ return 1;
+}
+
+int hdhomerun_device_channelscan_advance(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result)
+{
+ if (!hd->scan) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_advance: scan not initialized\n");
+ return 0;
+ }
+
+ int ret = channelscan_advance(hd->scan, result);
+ if (ret <= 0) { /* Free scan if normal finish or fatal error */
+ channelscan_destroy(hd->scan);
+ hd->scan = NULL;
+ }
+
+ return ret;
+}
+
+int hdhomerun_device_channelscan_detect(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result)
+{
+ if (!hd->scan) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_detect: scan not initialized\n");
+ return 0;
+ }
+
+ int ret = channelscan_detect(hd->scan, result);
+ if (ret < 0) { /* Free scan if fatal error */
+ channelscan_destroy(hd->scan);
+ hd->scan = NULL;
+ }
+
+ return ret;
+}
+
+uint8_t hdhomerun_device_channelscan_get_progress(struct hdhomerun_device_t *hd)
+{
+ if (!hd->scan) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_get_progress: scan not initialized\n");
+ return 0;
+ }
+
+ return channelscan_get_progress(hd->scan);
+}
+
+const char *hdhomerun_device_get_model_str(struct hdhomerun_device_t *hd)
+{
+ if (*hd->model) {
+ return hd->model;
+ }
+
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_model_str: device not set\n");
+ return NULL;
+ }
+
+ char *model_str;
+ int ret = hdhomerun_control_get(hd->cs, "/sys/model", &model_str, NULL);
+ if (ret < 0) {
+ return NULL;
+ }
+ if (ret == 0) {
+ strncpy(hd->model, "hdhomerun_atsc", sizeof(hd->model) - 1);
+ hd->model[sizeof(hd->model) - 1] = 0;
+ return hd->model;
+ }
+
+ strncpy(hd->model, model_str, sizeof(hd->model) - 1);
+ hd->model[sizeof(hd->model) - 1] = 0;
+ return hd->model;
+}
+
+int hdhomerun_device_upgrade(struct hdhomerun_device_t *hd, FILE *upgrade_file)
+{
+ if (!hd->cs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_upgrade: device not set\n");
+ return -1;
+ }
+
+ hdhomerun_control_set(hd->cs, "/tuner0/lockkey", "force", NULL, NULL);
+ hdhomerun_control_set(hd->cs, "/tuner0/channel", "none", NULL, NULL);
+
+ hdhomerun_control_set(hd->cs, "/tuner1/lockkey", "force", NULL, NULL);
+ hdhomerun_control_set(hd->cs, "/tuner1/channel", "none", NULL, NULL);
+
+ return hdhomerun_control_upgrade(hd->cs, upgrade_file);
+}
+
+void hdhomerun_device_debug_print_video_stats(struct hdhomerun_device_t *hd)
+{
+ if (!hdhomerun_debug_enabled(hd->dbg)) {
+ return;
+ }
+
+ if (hd->cs) {
+ char name[32];
+ sprintf(name, "/tuner%u/debug", hd->tuner);
+
+ char *debug_str;
+ char *error_str;
+ int ret = hdhomerun_control_get(hd->cs, name, &debug_str, &error_str);
+ if (ret < 0) {
+ hdhomerun_debug_printf(hd->dbg, "video dev: communication error getting debug stats\n");
+ return;
+ }
+
+ if (error_str) {
+ hdhomerun_debug_printf(hd->dbg, "video dev: %s\n", error_str);
+ } else {
+ hdhomerun_debug_printf(hd->dbg, "video dev: %s\n", debug_str);
+ }
+ }
+
+ if (hd->vs) {
+ hdhomerun_video_debug_print_stats(hd->vs);
+ }
+}
+
+void hdhomerun_device_get_video_stats(struct hdhomerun_device_t *hd, struct hdhomerun_video_stats_t *stats)
+{
+ if (!hd->vs) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_stream_flush: video not initialized\n");
+ memset(stats, 0, sizeof(struct hdhomerun_video_stats_t));
+ return;
+ }
+
+ hdhomerun_video_get_stats(hd->vs, stats);
+}
diff --git a/libhdhomerun/hdhomerun_device.h b/libhdhomerun/hdhomerun_device.h
new file mode 100755
index 0000000..51c2b86
--- /dev/null
+++ b/libhdhomerun/hdhomerun_device.h
@@ -0,0 +1,268 @@
+/*
+ * hdhomerun_device.h
+ *
+ * Copyright © 2006-2008 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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HDHOMERUN_DEVICE_MAX_TUNE_TO_LOCK_TIME 1500
+#define HDHOMERUN_DEVICE_MAX_LOCK_TO_DATA_TIME 2000
+#define HDHOMERUN_DEVICE_MAX_TUNE_TO_DATA_TIME (HDHOMERUN_DEVICE_MAX_TUNE_TO_LOCK_TIME + HDHOMERUN_DEVICE_MAX_LOCK_TO_DATA_TIME)
+
+#define HDHOMERUN_TARGET_PROTOCOL_UDP "udp"
+#define HDHOMERUN_TARGET_PROTOCOL_RTP "rtp"
+
+/*
+ * Create a device object.
+ *
+ * Typically a device object will be created for each tuner.
+ * It is valid to have multiple device objects communicating with a single HDHomeRun.
+ *
+ * For example, a threaded application that streams video from 4 tuners (2 HDHomeRun devices) and has
+ * GUI feedback to the user of the selected tuner might use 5 device objects: 4 for streaming video
+ * (one per thread) and one for the GUI display that can switch between tuners.
+ *
+ * This function will not attempt to connect to the device. The connection will be established when first used.
+ *
+ * uint32_t device_id = 32-bit device id of device. Set to HDHOMERUN_DEVICE_ID_WILDCARD to match any device ID.
+ * uint32_t device_ip = IP address of device. Set to 0 to auto-detect.
+ * unsigned int tuner = tuner index (0 or 1). Can be changed later by calling hdhomerun_device_set_tuner.
+ * struct hdhomerun_debug_t *dbg: Pointer to debug logging object. May be NULL.
+ *
+ * Returns a pointer to the newly created device object.
+ *
+ * When no longer needed, the socket should be destroyed by calling hdhomerun_device_destroy.
+ *
+ * The hdhomerun_device_create_from_str function creates a device object from the given device_str.
+ * The device_str parameter can be any of the following forms:
+ * <device id>
+ * <device id>-<tuner index>
+ * <ip address>
+ * If the tuner index is not included in the device_str then it is set to zero. Use hdhomerun_device_set_tuner
+ * or hdhomerun_device_set_tuner_from_str to set the tuner.
+ *
+ * The hdhomerun_device_set_tuner_from_str function sets the tuner from the given tuner_str.
+ * The tuner_str parameter can be any of the following forms:
+ * <tuner index>
+ * /tuner<tuner index>
+ */
+extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_create(uint32_t device_id, uint32_t device_ip, unsigned int tuner, struct hdhomerun_debug_t *dbg);
+extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_create_from_str(const char *device_str, struct hdhomerun_debug_t *dbg);
+extern LIBTYPE void hdhomerun_device_destroy(struct hdhomerun_device_t *hd);
+
+/*
+ * Get the device id, ip, or tuner of the device instance.
+ */
+extern LIBTYPE const char *hdhomerun_device_get_name(struct hdhomerun_device_t *hd);
+extern LIBTYPE uint32_t hdhomerun_device_get_device_id(struct hdhomerun_device_t *hd);
+extern LIBTYPE uint32_t hdhomerun_device_get_device_ip(struct hdhomerun_device_t *hd);
+extern LIBTYPE uint32_t hdhomerun_device_get_device_id_requested(struct hdhomerun_device_t *hd);
+extern LIBTYPE uint32_t hdhomerun_device_get_device_ip_requested(struct hdhomerun_device_t *hd);
+extern LIBTYPE unsigned int hdhomerun_device_get_tuner(struct hdhomerun_device_t *hd);
+
+extern LIBTYPE int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_id, uint32_t device_ip);
+extern LIBTYPE int hdhomerun_device_set_tuner(struct hdhomerun_device_t *hd, unsigned int tuner);
+extern LIBTYPE int hdhomerun_device_set_tuner_from_str(struct hdhomerun_device_t *hd, const char *tuner_str);
+
+/*
+ * Get the local machine IP address used when communicating with the device.
+ *
+ * This function is useful for determining the IP address to use with set target commands.
+ *
+ * Returns 32-bit IP address with native endianness, or 0 on error.
+ */
+extern LIBTYPE uint32_t hdhomerun_device_get_local_machine_addr(struct hdhomerun_device_t *hd);
+
+/*
+ * Get operations.
+ *
+ * struct hdhomerun_tuner_status_t *status = Pointer to caller supplied status struct to be populated with result.
+ * const char **p<name> = Caller supplied char * to be updated to point to the result string. The string will remain
+ * valid until another call to a device function.
+ *
+ * Returns 1 if the operation was successful.
+ * Returns 0 if the operation was rejected.
+ * Returns -1 if a communication error occurred.
+ */
+extern LIBTYPE int hdhomerun_device_get_tuner_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status);
+extern LIBTYPE int hdhomerun_device_get_tuner_vstatus(struct hdhomerun_device_t *hd, char **pvstatus_str, struct hdhomerun_tuner_vstatus_t *vstatus);
+extern LIBTYPE int hdhomerun_device_get_tuner_streaminfo(struct hdhomerun_device_t *hd, char **pstreaminfo);
+extern LIBTYPE int hdhomerun_device_get_tuner_channel(struct hdhomerun_device_t *hd, char **pchannel);
+extern LIBTYPE int hdhomerun_device_get_tuner_vchannel(struct hdhomerun_device_t *hd, char **pvchannel);
+extern LIBTYPE int hdhomerun_device_get_tuner_channelmap(struct hdhomerun_device_t *hd, char **pchannelmap);
+extern LIBTYPE int hdhomerun_device_get_tuner_filter(struct hdhomerun_device_t *hd, char **pfilter);
+extern LIBTYPE int hdhomerun_device_get_tuner_program(struct hdhomerun_device_t *hd, char **pprogram);
+extern LIBTYPE int hdhomerun_device_get_tuner_target(struct hdhomerun_device_t *hd, char **ptarget);
+extern LIBTYPE int hdhomerun_device_get_tuner_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount);
+extern LIBTYPE int hdhomerun_device_get_tuner_lockkey_owner(struct hdhomerun_device_t *hd, char **powner);
+extern LIBTYPE int hdhomerun_device_get_oob_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status);
+extern LIBTYPE int hdhomerun_device_get_oob_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount);
+extern LIBTYPE int hdhomerun_device_get_ir_target(struct hdhomerun_device_t *hd, char **ptarget);
+extern LIBTYPE int hdhomerun_device_get_lineup_location(struct hdhomerun_device_t *hd, char **plocation);
+extern LIBTYPE int hdhomerun_device_get_version(struct hdhomerun_device_t *hd, char **pversion_str, uint32_t *pversion_num);
+extern LIBTYPE int hdhomerun_device_get_supported(struct hdhomerun_device_t *hd, char *prefix, char **pstr);
+
+extern LIBTYPE uint32_t hdhomerun_device_get_tuner_status_ss_color(struct hdhomerun_tuner_status_t *status);
+extern LIBTYPE uint32_t hdhomerun_device_get_tuner_status_snq_color(struct hdhomerun_tuner_status_t *status);
+extern LIBTYPE uint32_t hdhomerun_device_get_tuner_status_seq_color(struct hdhomerun_tuner_status_t *status);
+
+extern LIBTYPE const char *hdhomerun_device_get_model_str(struct hdhomerun_device_t *hd);
+
+/*
+ * Set operations.
+ *
+ * const char *<name> = String to send to device.
+ *
+ * Returns 1 if the operation was successful.
+ * Returns 0 if the operation was rejected.
+ * Returns -1 if a communication error occurred.
+ */
+extern LIBTYPE int hdhomerun_device_set_tuner_channel(struct hdhomerun_device_t *hd, const char *channel);
+extern LIBTYPE int hdhomerun_device_set_tuner_vchannel(struct hdhomerun_device_t *hd, const char *vchannel);
+extern LIBTYPE int hdhomerun_device_set_tuner_channelmap(struct hdhomerun_device_t *hd, const char *channelmap);
+extern LIBTYPE int hdhomerun_device_set_tuner_filter(struct hdhomerun_device_t *hd, const char *filter);
+extern LIBTYPE int hdhomerun_device_set_tuner_filter_by_array(struct hdhomerun_device_t *hd, unsigned char filter_array[0x2000]);
+extern LIBTYPE int hdhomerun_device_set_tuner_program(struct hdhomerun_device_t *hd, const char *program);
+extern LIBTYPE int hdhomerun_device_set_tuner_target(struct hdhomerun_device_t *hd, const char *target);
+extern LIBTYPE int hdhomerun_device_set_ir_target(struct hdhomerun_device_t *hd, const char *target);
+extern LIBTYPE int hdhomerun_device_set_lineup_location(struct hdhomerun_device_t *hd, const char *location);
+extern LIBTYPE int hdhomerun_device_set_sys_dvbc_modulation(struct hdhomerun_device_t *hd, const char *modulation_list);
+
+/*
+ * Get/set a named control variable on the device.
+ *
+ * const char *name: The name of var to get/set (c-string). The supported vars is device/firmware dependant.
+ * const char *value: The value to set (c-string). The format is device/firmware dependant.
+
+ * char **pvalue: If provided, the caller-supplied char pointer will be populated with a pointer to the value
+ * string returned by the device, or NULL if the device returned an error string. The string will remain
+ * valid until the next call to a control sock function.
+ * char **perror: If provided, the caller-supplied char pointer will be populated with a pointer to the error
+ * string returned by the device, or NULL if the device returned an value string. The string will remain
+ * valid until the next call to a control sock function.
+ *
+ * Returns 1 if the operation was successful (pvalue set, perror NULL).
+ * Returns 0 if the operation was rejected (pvalue NULL, perror set).
+ * Returns -1 if a communication error occurs.
+ */
+extern LIBTYPE int hdhomerun_device_get_var(struct hdhomerun_device_t *hd, const char *name, char **pvalue, char **perror);
+extern LIBTYPE int hdhomerun_device_set_var(struct hdhomerun_device_t *hd, const char *name, const char *value, char **pvalue, char **perror);
+
+/*
+ * Tuner locking.
+ *
+ * The hdhomerun_device_tuner_lockkey_request function is used to obtain a lock
+ * or to verify that the hdhomerun_device object still holds the lock.
+ * Returns 1 if the lock request was successful and the lock was obtained.
+ * Returns 0 if the lock request was rejected.
+ * Returns -1 if a communication error occurs.
+ *
+ * The hdhomerun_device_tuner_lockkey_release function is used to release a
+ * previously held lock. If locking is used then this function must be called
+ * before destroying the hdhomerun_device object.
+ */
+extern LIBTYPE int hdhomerun_device_tuner_lockkey_request(struct hdhomerun_device_t *hd, char **perror);
+extern LIBTYPE int hdhomerun_device_tuner_lockkey_release(struct hdhomerun_device_t *hd);
+extern LIBTYPE int hdhomerun_device_tuner_lockkey_force(struct hdhomerun_device_t *hd);
+
+/*
+ * Intended only for non persistent connections; eg, hdhomerun_config.
+ */
+extern LIBTYPE void hdhomerun_device_tuner_lockkey_use_value(struct hdhomerun_device_t *hd, uint32_t lockkey);
+
+/*
+ * Wait for tuner lock after channel change.
+ *
+ * The hdhomerun_device_wait_for_lock function is used to detect/wait for a lock vs no lock indication
+ * after a channel change.
+ *
+ * It will return quickly if a lock is aquired.
+ * It will return quickly if there is no signal detected.
+ * Worst case it will time out after 1.5 seconds - the case where there is signal but no lock.
+ */
+extern LIBTYPE int hdhomerun_device_wait_for_lock(struct hdhomerun_device_t *hd, struct hdhomerun_tuner_status_t *status);
+
+/*
+ * Stream a filtered program or the unfiltered stream.
+ *
+ * The hdhomerun_device_stream_start function initializes the process and tells the device to start streamin data.
+ *
+ * uint16_t program_number = The program number to filer, or 0 for unfiltered.
+ *
+ * Returns 1 if the oprtation started successfully.
+ * Returns 0 if the operation was rejected.
+ * Returns -1 if a communication error occurs.
+ *
+ * The hdhomerun_device_stream_recv function should be called periodically to receive the stream data.
+ * The buffer can losslessly store 1 second of data, however a more typical call rate would be every 15ms.
+ *
+ * The hdhomerun_device_stream_stop function tells the device to stop streaming data.
+ */
+extern LIBTYPE int hdhomerun_device_stream_start(struct hdhomerun_device_t *hd);
+extern LIBTYPE uint8_t *hdhomerun_device_stream_recv(struct hdhomerun_device_t *hd, size_t max_size, size_t *pactual_size);
+extern LIBTYPE void hdhomerun_device_stream_flush(struct hdhomerun_device_t *hd);
+extern LIBTYPE void hdhomerun_device_stream_stop(struct hdhomerun_device_t *hd);
+
+/*
+ * Channel scan API.
+ */
+extern LIBTYPE int hdhomerun_device_channelscan_init(struct hdhomerun_device_t *hd, const char *channelmap);
+extern LIBTYPE int hdhomerun_device_channelscan_advance(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result);
+extern LIBTYPE int hdhomerun_device_channelscan_detect(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result);
+extern LIBTYPE uint8_t hdhomerun_device_channelscan_get_progress(struct hdhomerun_device_t *hd);
+
+/*
+ * Upload new firmware to the device.
+ *
+ * FILE *upgrade_file: File pointer to read from. The file must have been opened in binary mode for reading.
+ *
+ * Returns 1 if the upload succeeded.
+ * Returns 0 if the upload was rejected.
+ * Returns -1 if an error occurs.
+ */
+extern LIBTYPE int hdhomerun_device_upgrade(struct hdhomerun_device_t *hd, FILE *upgrade_file);
+
+/*
+ * Low level accessor functions.
+ */
+extern LIBTYPE struct hdhomerun_control_sock_t *hdhomerun_device_get_control_sock(struct hdhomerun_device_t *hd);
+extern LIBTYPE struct hdhomerun_video_sock_t *hdhomerun_device_get_video_sock(struct hdhomerun_device_t *hd);
+
+/*
+ * Debug print internal stats.
+ */
+extern LIBTYPE void hdhomerun_device_debug_print_video_stats(struct hdhomerun_device_t *hd);
+extern LIBTYPE void hdhomerun_device_get_video_stats(struct hdhomerun_device_t *hd, struct hdhomerun_video_stats_t *stats);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_device_selector.c b/libhdhomerun/hdhomerun_device_selector.c
new file mode 100755
index 0000000..f57f125
--- /dev/null
+++ b/libhdhomerun/hdhomerun_device_selector.c
@@ -0,0 +1,348 @@
+/*
+ * hdhomerun_device_selector.c
+ *
+ * Copyright © 2009-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_device_selector_t {
+ struct hdhomerun_device_t **hd_list;
+ size_t hd_count;
+ struct hdhomerun_debug_t *dbg;
+};
+
+struct hdhomerun_device_selector_t *hdhomerun_device_selector_create(struct hdhomerun_debug_t *dbg)
+{
+ struct hdhomerun_device_selector_t *hds = (struct hdhomerun_device_selector_t *)calloc(1, sizeof(struct hdhomerun_device_selector_t));
+ if (!hds) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_device_selector_create: failed to allocate selector object\n");
+ return NULL;
+ }
+
+ hds->dbg = dbg;
+
+ return hds;
+}
+
+void hdhomerun_device_selector_destroy(struct hdhomerun_device_selector_t *hds, bool_t destroy_devices)
+{
+ if (destroy_devices) {
+ size_t index;
+ for (index = 0; index < hds->hd_count; index++) {
+ struct hdhomerun_device_t *entry = hds->hd_list[index];
+ hdhomerun_device_destroy(entry);
+ }
+ }
+
+ if (hds->hd_list) {
+ free(hds->hd_list);
+ }
+
+ free(hds);
+}
+
+LIBTYPE int hdhomerun_device_selector_get_device_count(struct hdhomerun_device_selector_t *hds)
+{
+ return (int)hds->hd_count;
+}
+
+void hdhomerun_device_selector_add_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd)
+{
+ size_t index;
+ for (index = 0; index < hds->hd_count; index++) {
+ struct hdhomerun_device_t *entry = hds->hd_list[index];
+ if (entry == hd) {
+ return;
+ }
+ }
+
+ hds->hd_list = (struct hdhomerun_device_t **)realloc(hds->hd_list, (hds->hd_count + 1) * sizeof(struct hdhomerun_device_selector_t *));
+ if (!hds->hd_list) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_add_device: failed to allocate device list\n");
+ return;
+ }
+
+ hds->hd_list[hds->hd_count++] = hd;
+}
+
+void hdhomerun_device_selector_remove_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd)
+{
+ size_t index = 0;
+ while (1) {
+ if (index >= hds->hd_count) {
+ return;
+ }
+
+ struct hdhomerun_device_t *entry = hds->hd_list[index];
+ if (entry == hd) {
+ break;
+ }
+
+ index++;
+ }
+
+ while (index + 1 < hds->hd_count) {
+ hds->hd_list[index] = hds->hd_list[index + 1];
+ index++;
+ }
+
+ hds->hd_list[index] = NULL;
+ hds->hd_count--;
+}
+
+struct hdhomerun_device_t *hdhomerun_device_selector_find_device(struct hdhomerun_device_selector_t *hds, uint32_t device_id, unsigned int tuner_index)
+{
+ size_t index;
+ for (index = 0; index < hds->hd_count; index++) {
+ struct hdhomerun_device_t *entry = hds->hd_list[index];
+ if (hdhomerun_device_get_device_id(entry) != device_id) {
+ continue;
+ }
+ if (hdhomerun_device_get_tuner(entry) != tuner_index) {
+ continue;
+ }
+ return entry;
+ }
+
+ return NULL;
+}
+
+int hdhomerun_device_selector_load_from_file(struct hdhomerun_device_selector_t *hds, char *filename)
+{
+ FILE *fp = fopen(filename, "r");
+ if (!fp) {
+ return 0;
+ }
+
+ while(1) {
+ char device_name[32];
+ if (!fgets(device_name, sizeof(device_name), fp)) {
+ break;
+ }
+
+ struct hdhomerun_device_t *hd = hdhomerun_device_create_from_str(device_name, hds->dbg);
+ if (!hd) {
+ continue;
+ }
+
+ hdhomerun_device_selector_add_device(hds, hd);
+ }
+
+ fclose(fp);
+ return (int)hds->hd_count;
+}
+
+#if defined(__WINDOWS__)
+int hdhomerun_device_selector_load_from_windows_registry(struct hdhomerun_device_selector_t *hds, wchar_t *wsource)
+{
+ HKEY tuners_key;
+ LONG ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Silicondust\\HDHomeRun\\Tuners", 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &tuners_key);
+ if (ret != ERROR_SUCCESS) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_load_from_windows_registry: failed to open tuners registry key (%ld)\n", (long)ret);
+ return 0;
+ }
+
+ DWORD index = 0;
+ while (1) {
+ /* Next tuner device. */
+ wchar_t wdevice_name[32];
+ DWORD size = sizeof(wdevice_name);
+ ret = RegEnumKeyEx(tuners_key, index++, wdevice_name, &size, NULL, NULL, NULL, NULL);
+ if (ret != ERROR_SUCCESS) {
+ break;
+ }
+
+ /* Check device configuation. */
+ HKEY device_key;
+ ret = RegOpenKeyEx(tuners_key, wdevice_name, 0, KEY_QUERY_VALUE, &device_key);
+ if (ret != ERROR_SUCCESS) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_load_from_windows_registry: failed to open registry key for %S (%ld)\n", wdevice_name, (long)ret);
+ continue;
+ }
+
+ wchar_t wsource_test[32];
+ size = sizeof(wsource_test);
+ if (RegQueryValueEx(device_key, L"Source", NULL, NULL, (LPBYTE)&wsource_test, &size) != ERROR_SUCCESS) {
+ wsprintf(wsource_test, L"Unknown");
+ }
+
+ RegCloseKey(device_key);
+
+ if (_wcsicmp(wsource_test, wsource) != 0) {
+ continue;
+ }
+
+ /* Create and add device. */
+ char device_name[32];
+ sprintf(device_name, "%S", wdevice_name);
+
+ struct hdhomerun_device_t *hd = hdhomerun_device_create_from_str(device_name, hds->dbg);
+ if (!hd) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_load_from_windows_registry: invalid device name '%s' / failed to create device object\n", device_name);
+ continue;
+ }
+
+ hdhomerun_device_selector_add_device(hds, hd);
+ }
+
+ RegCloseKey(tuners_key);
+ return (int)hds->hd_count;
+}
+#endif
+
+static bool_t hdhomerun_device_selector_choose_test(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *test_hd)
+{
+ const char *name = hdhomerun_device_get_name(test_hd);
+
+ /*
+ * Attempt to aquire lock.
+ */
+ char *error;
+ int ret = hdhomerun_device_tuner_lockkey_request(test_hd, &error);
+ if (ret > 0) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s chosen\n", name);
+ return TRUE;
+ }
+ if (ret < 0) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s communication error\n", name);
+ return FALSE;
+ }
+
+ /*
+ * In use - check target.
+ */
+ char *target;
+ ret = hdhomerun_device_get_tuner_target(test_hd, &target);
+ if (ret < 0) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s communication error\n", name);
+ return FALSE;
+ }
+ if (ret == 0) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use, failed to read target\n", name);
+ return FALSE;
+ }
+
+ char *ptr = strstr(target, "//");
+ if (ptr) {
+ target = ptr + 2;
+ }
+ ptr = strchr(target, ' ');
+ if (ptr) {
+ *ptr = 0;
+ }
+
+ unsigned long a[4];
+ unsigned long target_port;
+ if (sscanf(target, "%lu.%lu.%lu.%lu:%lu", &a[0], &a[1], &a[2], &a[3], &target_port) != 5) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use, no target set (%s)\n", name, target);
+ return FALSE;
+ }
+
+ uint32_t target_ip = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0));
+ uint32_t local_ip = hdhomerun_device_get_local_machine_addr(test_hd);
+ if (target_ip != local_ip) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by %s\n", name, target);
+ return FALSE;
+ }
+
+ /*
+ * Test local port.
+ */
+ hdhomerun_sock_t test_sock = hdhomerun_sock_create_udp();
+ if (test_sock == HDHOMERUN_SOCK_INVALID) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use, failed to create test sock\n", name);
+ return FALSE;
+ }
+
+ bool_t inuse = (hdhomerun_sock_bind(test_sock, INADDR_ANY, (uint16_t)target_port, FALSE) == FALSE);
+ hdhomerun_sock_destroy(test_sock);
+
+ if (inuse) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by local machine\n", name);
+ return FALSE;
+ }
+
+ /*
+ * Dead local target, force clear lock.
+ */
+ ret = hdhomerun_device_tuner_lockkey_force(test_hd);
+ if (ret < 0) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s communication error\n", name);
+ return FALSE;
+ }
+ if (ret == 0) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by local machine, dead target, failed to force release lockkey\n", name);
+ return FALSE;
+ }
+
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by local machine, dead target, lockkey force successful\n", name);
+
+ /*
+ * Attempt to aquire lock.
+ */
+ ret = hdhomerun_device_tuner_lockkey_request(test_hd, &error);
+ if (ret > 0) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s chosen\n", name);
+ return TRUE;
+ }
+ if (ret < 0) {
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s communication error\n", name);
+ return FALSE;
+ }
+
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s still in use after lockkey force (%s)\n", name, error);
+ return FALSE;
+}
+
+struct hdhomerun_device_t *hdhomerun_device_selector_choose_and_lock(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *prefered)
+{
+ /* Test prefered device first. */
+ if (prefered) {
+ if (hdhomerun_device_selector_choose_test(hds, prefered)) {
+ return prefered;
+ }
+ }
+
+ /* Test other tuners. */
+ size_t index;
+ for (index = 0; index < hds->hd_count; index++) {
+ struct hdhomerun_device_t *entry = hds->hd_list[index];
+ if (entry == prefered) {
+ continue;
+ }
+
+ if (hdhomerun_device_selector_choose_test(hds, entry)) {
+ return entry;
+ }
+ }
+
+ hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_and_lock: no devices available\n");
+ return NULL;
+}
diff --git a/libhdhomerun/hdhomerun_device_selector.h b/libhdhomerun/hdhomerun_device_selector.h
new file mode 100755
index 0000000..2f3587c
--- /dev/null
+++ b/libhdhomerun/hdhomerun_device_selector.h
@@ -0,0 +1,97 @@
+/*
+ * hdhomerun_device_selector.h
+ *
+ * Copyright © 2009 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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Create a device selector object for use with dynamic tuner allocation support.
+ * All tuners registered with a specific device selector instance must have the same signal source.
+ * The dbg parameter may be null.
+ */
+extern LIBTYPE struct hdhomerun_device_selector_t *hdhomerun_device_selector_create(struct hdhomerun_debug_t *dbg);
+extern LIBTYPE void hdhomerun_device_selector_destroy(struct hdhomerun_device_selector_t *hds, bool_t destroy_devices);
+
+/*
+ * Get the number of devices in the list.
+ */
+extern LIBTYPE int hdhomerun_device_selector_get_device_count(struct hdhomerun_device_selector_t *hds);
+
+/*
+ * Populate device selector with devices from given source.
+ * Returns the number of devices populated.
+ */
+extern LIBTYPE int hdhomerun_device_selector_load_from_file(struct hdhomerun_device_selector_t *hds, char *filename);
+#if defined(__WINDOWS__)
+extern LIBTYPE int hdhomerun_device_selector_load_from_windows_registry(struct hdhomerun_device_selector_t *hds, wchar_t *wsource);
+#endif
+
+/*
+ * Add/remove a device from the selector list.
+ */
+extern LIBTYPE void hdhomerun_device_selector_add_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd);
+extern LIBTYPE void hdhomerun_device_selector_remove_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd);
+
+/*
+ * Find a device in the selector list.
+ */
+extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_selector_find_device(struct hdhomerun_device_selector_t *hds, uint32_t device_id, unsigned int tuner_index);
+
+/*
+ * Select and lock an available device.
+ * If not null, preference will be given to the prefered device specified.
+ * The device resource lock must be released by the application when no longer needed by
+ * calling hdhomerun_device_tuner_lockkey_release().
+ *
+ * Recommended channel change logic:
+ *
+ * Start (inactive -> active):
+ * - Call hdhomerun_device_selector_choose_and_lock() to choose and lock an available tuner.
+ *
+ * Stop (active -> inactive):
+ * - Call hdhomerun_device_tuner_lockkey_release() to release the resource lock and allow the tuner
+ * to be allocated by other computers.
+ *
+ * Channel change (active -> active):
+ * - If the new channel has a different signal source then call hdhomerun_device_tuner_lockkey_release()
+ * to release the lock on the tuner playing the previous channel, then call
+ * hdhomerun_device_selector_choose_and_lock() to choose and lock an available tuner.
+ * - If the new channel has the same signal source then call hdhomerun_device_tuner_lockkey_request()
+ * to refresh the lock. If this function succeeds then the same device can be used. If this fucntion fails
+ * then call hdhomerun_device_selector_choose_and_lock() to choose and lock an available tuner.
+ */
+extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_selector_choose_and_lock(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *prefered);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_discover.c b/libhdhomerun/hdhomerun_discover.c
new file mode 100755
index 0000000..0307013
--- /dev/null
+++ b/libhdhomerun/hdhomerun_discover.c
@@ -0,0 +1,439 @@
+/*
+ * hdhomerun_discover.c
+ *
+ * Copyright © 2006-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"
+
+#define HDHOMERUN_DISOCVER_MAX_SOCK_COUNT 16
+
+struct hdhomerun_discover_sock_t {
+ hdhomerun_sock_t sock;
+ bool_t detected;
+ uint32_t local_ip;
+ uint32_t subnet_mask;
+};
+
+struct hdhomerun_discover_t {
+ struct hdhomerun_discover_sock_t socks[HDHOMERUN_DISOCVER_MAX_SOCK_COUNT];
+ unsigned int sock_count;
+ struct hdhomerun_pkt_t tx_pkt;
+ struct hdhomerun_pkt_t rx_pkt;
+};
+
+static bool_t hdhomerun_discover_sock_add(struct hdhomerun_discover_t *ds, uint32_t local_ip, uint32_t subnet_mask)
+{
+ unsigned int i;
+ for (i = 1; i < ds->sock_count; i++) {
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
+
+ if ((dss->local_ip == local_ip) && (dss->subnet_mask == subnet_mask)) {
+ dss->detected = TRUE;
+ return TRUE;
+ }
+ }
+
+ if (ds->sock_count >= HDHOMERUN_DISOCVER_MAX_SOCK_COUNT) {
+ return FALSE;
+ }
+
+ /* Create socket. */
+ hdhomerun_sock_t sock = hdhomerun_sock_create_udp();
+ if (sock == HDHOMERUN_SOCK_INVALID) {
+ return FALSE;
+ }
+
+ /* Bind socket. */
+ if (!hdhomerun_sock_bind(sock, local_ip, 0, FALSE)) {
+ hdhomerun_sock_destroy(sock);
+ return FALSE;
+ }
+
+ /* Write sock entry. */
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[ds->sock_count++];
+ dss->sock = sock;
+ dss->detected = TRUE;
+ dss->local_ip = local_ip;
+ dss->subnet_mask = subnet_mask;
+
+ return TRUE;
+}
+
+struct hdhomerun_discover_t *hdhomerun_discover_create(void)
+{
+ struct hdhomerun_discover_t *ds = (struct hdhomerun_discover_t *)calloc(1, sizeof(struct hdhomerun_discover_t));
+ if (!ds) {
+ return NULL;
+ }
+
+ /* Create a routable socket (always first entry). */
+ if (!hdhomerun_discover_sock_add(ds, 0, 0)) {
+ free(ds);
+ return NULL;
+ }
+
+ /* Success. */
+ return ds;
+}
+
+void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds)
+{
+ unsigned int i;
+ for (i = 0; i < ds->sock_count; i++) {
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
+ hdhomerun_sock_destroy(dss->sock);
+ }
+
+ free(ds);
+}
+
+static void hdhomerun_discover_sock_detect(struct hdhomerun_discover_t *ds)
+{
+ unsigned int i;
+ for (i = 1; i < ds->sock_count; i++) {
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
+ dss->detected = FALSE;
+ }
+
+ struct hdhomerun_local_ip_info_t ip_info_list[HDHOMERUN_DISOCVER_MAX_SOCK_COUNT];
+ int count = hdhomerun_local_ip_info(ip_info_list, HDHOMERUN_DISOCVER_MAX_SOCK_COUNT);
+ if (count < 0) {
+ count = 0;
+ }
+
+ int index;
+ for (index = 0; index < count; index++) {
+ struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[index];
+ hdhomerun_discover_sock_add(ds, ip_info->ip_addr, ip_info->subnet_mask);
+ }
+
+ struct hdhomerun_discover_sock_t *src = &ds->socks[1];
+ struct hdhomerun_discover_sock_t *dst = &ds->socks[1];
+ count = 1;
+ for (i = 1; i < ds->sock_count; i++) {
+ if (!src->detected) {
+ hdhomerun_sock_destroy(src->sock);
+ src++;
+ continue;
+ }
+ if (dst != src) {
+ *dst = *src;
+ }
+ src++;
+ dst++;
+ count++;
+ }
+
+ ds->sock_count = count;
+}
+
+static bool_t hdhomerun_discover_send_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, uint32_t target_ip, uint32_t device_type, uint32_t device_id)
+{
+ struct hdhomerun_pkt_t *tx_pkt = &ds->tx_pkt;
+ hdhomerun_pkt_reset(tx_pkt);
+
+ hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_TYPE);
+ hdhomerun_pkt_write_var_length(tx_pkt, 4);
+ hdhomerun_pkt_write_u32(tx_pkt, device_type);
+ hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_ID);
+ hdhomerun_pkt_write_var_length(tx_pkt, 4);
+ hdhomerun_pkt_write_u32(tx_pkt, device_id);
+ hdhomerun_pkt_seal_frame(tx_pkt, HDHOMERUN_TYPE_DISCOVER_REQ);
+
+ return hdhomerun_sock_sendto(dss->sock, target_ip, HDHOMERUN_DISCOVER_UDP_PORT, tx_pkt->start, tx_pkt->end - tx_pkt->start, 0);
+}
+
+static bool_t hdhomerun_discover_send_wildcard_ip(struct hdhomerun_discover_t *ds, uint32_t device_type, uint32_t device_id)
+{
+ bool_t result = FALSE;
+
+ /*
+ * Send subnet broadcast using each local ip socket.
+ * This will work with multiple separate 169.254.x.x interfaces.
+ */
+ unsigned int i;
+ for (i = 1; i < ds->sock_count; i++) {
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
+ uint32_t target_ip = dss->local_ip | ~dss->subnet_mask;
+ result |= hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id);
+ }
+
+ /*
+ * If no local ip sockets then fall back to sending a global broadcast letting the OS choose the interface.
+ */
+ if (!result) {
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[0];
+ result = hdhomerun_discover_send_internal(ds, dss, 0xFFFFFFFF, device_type, device_id);
+ }
+
+ return result;
+}
+
+static bool_t hdhomerun_discover_send_target_ip(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id)
+{
+ bool_t result = FALSE;
+
+ /*
+ * Send targeted packet from any local ip that is in the same subnet.
+ * This will work with multiple separate 169.254.x.x interfaces.
+ */
+ unsigned int i;
+ for (i = 1; i < ds->sock_count; i++) {
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
+ if ((target_ip & dss->subnet_mask) != (dss->local_ip & dss->subnet_mask)) {
+ continue;
+ }
+
+ result |= hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id);
+ }
+
+ /*
+ * If target IP does not match a local subnet then fall back to letting the OS choose the gateway interface.
+ */
+ if (!result) {
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[0];
+ result = hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id);
+ }
+
+ return result;
+}
+
+static bool_t hdhomerun_discover_send(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id)
+{
+ if (target_ip == 0) {
+ return hdhomerun_discover_send_wildcard_ip(ds, device_type, device_id);
+ } else {
+ return hdhomerun_discover_send_target_ip(ds, target_ip, device_type, device_id);
+ }
+}
+
+static bool_t hdhomerun_discover_recv_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, struct hdhomerun_discover_device_t *result)
+{
+ struct hdhomerun_pkt_t *rx_pkt = &ds->rx_pkt;
+ hdhomerun_pkt_reset(rx_pkt);
+
+ uint32_t remote_addr;
+ uint16_t remote_port;
+ size_t length = rx_pkt->limit - rx_pkt->end;
+ if (!hdhomerun_sock_recvfrom(dss->sock, &remote_addr, &remote_port, rx_pkt->end, &length, 0)) {
+ return FALSE;
+ }
+
+ rx_pkt->end += length;
+
+ uint16_t type;
+ if (hdhomerun_pkt_open_frame(rx_pkt, &type) <= 0) {
+ return FALSE;
+ }
+ if (type != HDHOMERUN_TYPE_DISCOVER_RPY) {
+ return FALSE;
+ }
+
+ result->ip_addr = remote_addr;
+ result->device_type = 0;
+ result->device_id = 0;
+ result->tuner_count = 0;
+
+ while (1) {
+ uint8_t tag;
+ size_t len;
+ uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len);
+ if (!next) {
+ break;
+ }
+
+ switch (tag) {
+ case HDHOMERUN_TAG_DEVICE_TYPE:
+ if (len != 4) {
+ break;
+ }
+ result->device_type = hdhomerun_pkt_read_u32(rx_pkt);
+ break;
+
+ case HDHOMERUN_TAG_DEVICE_ID:
+ if (len != 4) {
+ break;
+ }
+ result->device_id = hdhomerun_pkt_read_u32(rx_pkt);
+ break;
+
+ case HDHOMERUN_TAG_TUNER_COUNT:
+ if (len != 1) {
+ break;
+ }
+ result->tuner_count = hdhomerun_pkt_read_u8(rx_pkt);
+ break;
+
+ default:
+ break;
+ }
+
+ rx_pkt->pos = next;
+ }
+
+ /* Fixup for old firmware. */
+ if (result->tuner_count == 0) {
+ switch (result->device_id >> 20) {
+ case 0x102:
+ result->tuner_count = 1;
+ break;
+
+ case 0x100:
+ case 0x101:
+ case 0x121:
+ result->tuner_count = 2;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+static bool_t hdhomerun_discover_recv(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_device_t *result)
+{
+ unsigned int i;
+ for (i = 0; i < ds->sock_count; i++) {
+ struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
+
+ if (hdhomerun_discover_recv_internal(ds, dss, result)) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static struct hdhomerun_discover_device_t *hdhomerun_discover_find_in_list(struct hdhomerun_discover_device_t result_list[], int count, struct hdhomerun_discover_device_t *lookup)
+{
+ int index;
+ for (index = 0; index < count; index++) {
+ struct hdhomerun_discover_device_t *entry = &result_list[index];
+ if (memcmp(lookup, entry, sizeof(struct hdhomerun_discover_device_t)) == 0) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+int hdhomerun_discover_find_devices(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count)
+{
+ hdhomerun_discover_sock_detect(ds);
+
+ int count = 0;
+ int attempt;
+ for (attempt = 0; attempt < 2; attempt++) {
+ if (!hdhomerun_discover_send(ds, target_ip, device_type, device_id)) {
+ return -1;
+ }
+
+ uint64_t timeout = getcurrenttime() + 200;
+ while (1) {
+ struct hdhomerun_discover_device_t *result = &result_list[count];
+ memset(result, 0, sizeof(struct hdhomerun_discover_device_t));
+
+ if (!hdhomerun_discover_recv(ds, result)) {
+ if (getcurrenttime() >= timeout) {
+ break;
+ }
+ msleep_approx(10);
+ continue;
+ }
+
+ /* Filter. */
+ if (device_type != HDHOMERUN_DEVICE_TYPE_WILDCARD) {
+ if (device_type != result->device_type) {
+ continue;
+ }
+ }
+ if (device_id != HDHOMERUN_DEVICE_ID_WILDCARD) {
+ if (device_id != result->device_id) {
+ continue;
+ }
+ }
+
+ /* Ensure not already in list. */
+ if (hdhomerun_discover_find_in_list(result_list, count, result)) {
+ continue;
+ }
+
+ /* Add to list. */
+ count++;
+ if (count >= max_count) {
+ return count;
+ }
+ }
+ }
+
+ return count;
+}
+
+int hdhomerun_discover_find_devices_custom(uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count)
+{
+ if (hdhomerun_discover_is_ip_multicast(target_ip)) {
+ return 0;
+ }
+
+ struct hdhomerun_discover_t *ds = hdhomerun_discover_create();
+ if (!ds) {
+ return -1;
+ }
+
+ int ret = hdhomerun_discover_find_devices(ds, target_ip, device_type, device_id, result_list, max_count);
+
+ hdhomerun_discover_destroy(ds);
+ return ret;
+}
+
+bool_t hdhomerun_discover_validate_device_id(uint32_t device_id)
+{
+ static uint32_t lookup_table[16] = {0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0};
+
+ uint32_t checksum = 0;
+
+ checksum ^= lookup_table[(device_id >> 28) & 0x0F];
+ checksum ^= (device_id >> 24) & 0x0F;
+ checksum ^= lookup_table[(device_id >> 20) & 0x0F];
+ checksum ^= (device_id >> 16) & 0x0F;
+ checksum ^= lookup_table[(device_id >> 12) & 0x0F];
+ checksum ^= (device_id >> 8) & 0x0F;
+ checksum ^= lookup_table[(device_id >> 4) & 0x0F];
+ checksum ^= (device_id >> 0) & 0x0F;
+
+ return (checksum == 0);
+}
+
+bool_t hdhomerun_discover_is_ip_multicast(uint32_t ip_addr)
+{
+ return (ip_addr >= 0xE0000000) && (ip_addr < 0xF0000000);
+}
diff --git a/libhdhomerun/hdhomerun_discover.h b/libhdhomerun/hdhomerun_discover.h
new file mode 100755
index 0000000..2f0d906
--- /dev/null
+++ b/libhdhomerun/hdhomerun_discover.h
@@ -0,0 +1,87 @@
+/*
+ * hdhomerun_discover.h
+ *
+ * Copyright © 2006-2007 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.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct hdhomerun_discover_device_t {
+ uint32_t ip_addr;
+ uint32_t device_type;
+ uint32_t device_id;
+ uint8_t tuner_count;
+};
+
+/*
+ * Find devices.
+ *
+ * The device information is stored in caller-supplied array of hdhomerun_discover_device_t vars.
+ * Multiple attempts are made to find devices.
+ * Execution time is typically 400ms if max_count is not reached.
+ *
+ * Set target_ip to zero to auto-detect the IP address.
+ * Set device_type to HDHOMERUN_DEVICE_TYPE_TUNER to detect HDHomeRun tuner devices.
+ * Set device_id to HDHOMERUN_DEVICE_ID_WILDCARD to detect all device ids.
+ *
+ * Returns the number of devices found.
+ * Retruns -1 on error.
+ */
+extern LIBTYPE int hdhomerun_discover_find_devices_custom(uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count);
+
+/*
+ * Optional: persistent discover instance available for discover polling use.
+ */
+extern LIBTYPE struct hdhomerun_discover_t *hdhomerun_discover_create(void);
+extern LIBTYPE void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds);
+extern LIBTYPE int hdhomerun_discover_find_devices(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count);
+
+/*
+ * Verify that the device ID given is valid.
+ *
+ * The device ID contains a self-check sequence that detects common user input errors including
+ * single-digit errors and two digit transposition errors.
+ *
+ * Returns TRUE if valid.
+ * Returns FALSE if not valid.
+ */
+extern LIBTYPE bool_t hdhomerun_discover_validate_device_id(uint32_t device_id);
+
+/*
+ * Detect if an IP address is multicast.
+ *
+ * Returns TRUE if multicast.
+ * Returns FALSE if zero, unicast, expermental, or broadcast.
+ */
+extern LIBTYPE bool_t hdhomerun_discover_is_ip_multicast(uint32_t ip_addr);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_os.h b/libhdhomerun/hdhomerun_os.h
new file mode 100755
index 0000000..1506e07
--- /dev/null
+++ b/libhdhomerun/hdhomerun_os.h
@@ -0,0 +1,49 @@
+/*
+ * hdhomerun_os.h
+ *
+ * Copyright © 2006-2008 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.
+ */
+
+#if defined(_WIN32) || defined(_WIN64)
+#define __WINDOWS__
+#endif
+
+#if defined(__WINDOWS__)
+#include "hdhomerun_os_windows.h"
+#else
+#include "hdhomerun_os_posix.h"
+#endif
+
+#if !defined(TRUE)
+#define TRUE 1
+#endif
+
+#if !defined(FALSE)
+#define FALSE 0
+#endif
diff --git a/libhdhomerun/hdhomerun_os_posix.c b/libhdhomerun/hdhomerun_os_posix.c
new file mode 100755
index 0000000..25eece8
--- /dev/null
+++ b/libhdhomerun/hdhomerun_os_posix.c
@@ -0,0 +1,105 @@
+/*
+ * hdhomerun_os_posix.c
+ *
+ * Copyright © 2006-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_os.h"
+
+uint32_t random_get32(void)
+{
+ FILE *fp = fopen("/dev/urandom", "rb");
+ if (!fp) {
+ return (uint32_t)rand();
+ }
+
+ uint32_t Result;
+ if (fread(&Result, 4, 1, fp) != 1) {
+ Result = (uint32_t)rand();
+ }
+
+ fclose(fp);
+ return Result;
+}
+
+uint64_t getcurrenttime(void)
+{
+ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+ static uint64_t result = 0;
+ static uint64_t previous_time = 0;
+
+ pthread_mutex_lock(&lock);
+
+#if defined(CLOCK_MONOTONIC)
+ struct timespec tp;
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+ uint64_t current_time = ((uint64_t)tp.tv_sec * 1000) + (tp.tv_nsec / 1000000);
+#else
+ struct timeval t;
+ gettimeofday(&t, NULL);
+ uint64_t current_time = ((uint64_t)t.tv_sec * 1000) + (t.tv_usec / 1000);
+#endif
+
+ if (current_time > previous_time) {
+ result += current_time - previous_time;
+ }
+
+ previous_time = current_time;
+
+ pthread_mutex_unlock(&lock);
+ return result;
+}
+
+void msleep_approx(uint64_t ms)
+{
+ unsigned int delay_s = ms / 1000;
+ if (delay_s > 0) {
+ sleep(delay_s);
+ ms -= delay_s * 1000;
+ }
+
+ unsigned int delay_us = ms * 1000;
+ if (delay_us > 0) {
+ usleep(delay_us);
+ }
+}
+
+void msleep_minimum(uint64_t ms)
+{
+ uint64_t stop_time = getcurrenttime() + ms;
+
+ while (1) {
+ uint64_t current_time = getcurrenttime();
+ if (current_time >= stop_time) {
+ return;
+ }
+
+ msleep_approx(stop_time - current_time);
+ }
+}
diff --git a/libhdhomerun/hdhomerun_os_posix.h b/libhdhomerun/hdhomerun_os_posix.h
new file mode 100755
index 0000000..2969b20
--- /dev/null
+++ b/libhdhomerun/hdhomerun_os_posix.h
@@ -0,0 +1,71 @@
+/*
+ * hdhomerun_os_posix.h
+ *
+ * Copyright © 2006-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.
+ */
+
+#define _FILE_OFFSET_BITS 64
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/timeb.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <pthread.h>
+
+typedef int bool_t;
+typedef void (*sig_t)(int);
+
+#define LIBTYPE
+#define console_vprintf vprintf
+#define console_printf printf
+#define THREAD_FUNC_PREFIX void *
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern LIBTYPE uint32_t random_get32(void);
+extern LIBTYPE uint64_t getcurrenttime(void);
+extern LIBTYPE void msleep_approx(uint64_t ms);
+extern LIBTYPE void msleep_minimum(uint64_t ms);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_os_windows.c b/libhdhomerun/hdhomerun_os_windows.c
new file mode 100755
index 0000000..5f844db
--- /dev/null
+++ b/libhdhomerun/hdhomerun_os_windows.c
@@ -0,0 +1,149 @@
+/*
+ * hdhomerun_os_windows.c
+ *
+ * Copyright © 2006-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_os.h"
+
+uint32_t random_get32(void)
+{
+ HCRYPTPROV hProv;
+ if (!CryptAcquireContext(&hProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
+ return (uint32_t)rand();
+ }
+
+ uint32_t Result;
+ CryptGenRandom(hProv, sizeof(Result), (BYTE*)&Result);
+
+ CryptReleaseContext(hProv, 0);
+ return Result;
+}
+
+uint64_t getcurrenttime(void)
+{
+ static pthread_mutex_t lock = INVALID_HANDLE_VALUE;
+ static uint64_t result = 0;
+ static uint32_t previous_time = 0;
+
+ /* Initialization is not thread safe. */
+ if (lock == INVALID_HANDLE_VALUE) {
+ pthread_mutex_init(&lock, NULL);
+ }
+
+ pthread_mutex_lock(&lock);
+
+ uint32_t current_time = GetTickCount();
+
+ if (current_time > previous_time) {
+ result += current_time - previous_time;
+ }
+
+ previous_time = current_time;
+
+ pthread_mutex_unlock(&lock);
+ return result;
+}
+
+void msleep_approx(uint64_t ms)
+{
+ Sleep((DWORD)ms);
+}
+
+void msleep_minimum(uint64_t ms)
+{
+ uint64_t stop_time = getcurrenttime() + ms;
+
+ while (1) {
+ uint64_t current_time = getcurrenttime();
+ if (current_time >= stop_time) {
+ return;
+ }
+
+ msleep_approx(stop_time - current_time);
+ }
+}
+
+int pthread_create(pthread_t *tid, void *attr, LPTHREAD_START_ROUTINE start, void *arg)
+{
+ *tid = CreateThread(NULL, 0, start, arg, 0, NULL);
+ if (!*tid) {
+ return (int)GetLastError();
+ }
+ return 0;
+}
+
+int pthread_join(pthread_t tid, void **value_ptr)
+{
+ while (1) {
+ DWORD ExitCode = 0;
+ if (!GetExitCodeThread(tid, &ExitCode)) {
+ return (int)GetLastError();
+ }
+ if (ExitCode != STILL_ACTIVE) {
+ return 0;
+ }
+ }
+}
+
+void pthread_mutex_init(pthread_mutex_t *mutex, void *attr)
+{
+ *mutex = CreateMutex(NULL, FALSE, NULL);
+}
+
+void pthread_mutex_lock(pthread_mutex_t *mutex)
+{
+ WaitForSingleObject(*mutex, INFINITE);
+}
+
+void pthread_mutex_unlock(pthread_mutex_t *mutex)
+{
+ ReleaseMutex(*mutex);
+}
+
+/*
+ * The console output format should be set to UTF-8, however in XP and Vista this breaks batch file processing.
+ * Attempting to restore on exit fails to restore if the program is terminated by the user.
+ * Solution - set the output format each printf.
+ */
+void console_vprintf(const char *fmt, va_list ap)
+{
+ UINT cp = GetConsoleOutputCP();
+ SetConsoleOutputCP(CP_UTF8);
+ vprintf(fmt, ap);
+ SetConsoleOutputCP(cp);
+}
+
+void console_printf(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ console_vprintf(fmt, ap);
+ va_end(ap);
+}
diff --git a/libhdhomerun/hdhomerun_os_windows.h b/libhdhomerun/hdhomerun_os_windows.h
new file mode 100755
index 0000000..ff9e1d4
--- /dev/null
+++ b/libhdhomerun/hdhomerun_os_windows.h
@@ -0,0 +1,102 @@
+/*
+ * hdhomerun_os_windows.h
+ *
+ * Copyright © 2006-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.
+ */
+
+#define _WINSOCKAPI_
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <wspiapi.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/timeb.h>
+
+#if defined(DLL_IMPORT)
+#define LIBTYPE __declspec( dllexport )
+#elif defined(DLL_EXPORT)
+#define LIBTYPE __declspec( dllimport )
+#else
+#define LIBTYPE
+#endif
+
+typedef int bool_t;
+typedef signed __int8 int8_t;
+typedef signed __int16 int16_t;
+typedef signed __int32 int32_t;
+typedef signed __int64 int64_t;
+typedef unsigned __int8 uint8_t;
+typedef unsigned __int16 uint16_t;
+typedef unsigned __int32 uint32_t;
+typedef unsigned __int64 uint64_t;
+typedef void (*sig_t)(int);
+typedef HANDLE pthread_t;
+typedef HANDLE pthread_mutex_t;
+
+#define va_copy(x, y) x = y
+#define atoll _atoi64
+#define strdup _strdup
+#define strcasecmp _stricmp
+#define snprintf _snprintf
+#define fseeko _fseeki64
+#define ftello _ftelli64
+#define THREAD_FUNC_PREFIX DWORD WINAPI
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern LIBTYPE uint32_t random_get32(void);
+extern LIBTYPE uint64_t getcurrenttime(void);
+extern LIBTYPE void msleep_approx(uint64_t ms);
+extern LIBTYPE void msleep_minimum(uint64_t ms);
+
+extern LIBTYPE int pthread_create(pthread_t *tid, void *attr, LPTHREAD_START_ROUTINE start, void *arg);
+extern LIBTYPE int pthread_join(pthread_t tid, void **value_ptr);
+extern LIBTYPE void pthread_mutex_init(pthread_mutex_t *mutex, void *attr);
+extern LIBTYPE void pthread_mutex_lock(pthread_mutex_t *mutex);
+extern LIBTYPE void pthread_mutex_unlock(pthread_mutex_t *mutex);
+
+/*
+ * The console output format should be set to UTF-8, however in XP and Vista this breaks batch file processing.
+ * Attempting to restore on exit fails to restore if the program is terminated by the user.
+ * Solution - set the output format each printf.
+ */
+extern LIBTYPE void console_vprintf(const char *fmt, va_list ap);
+extern LIBTYPE void console_printf(const char *fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_pkt.c b/libhdhomerun/hdhomerun_pkt.c
new file mode 100755
index 0000000..bc34ba2
--- /dev/null
+++ b/libhdhomerun/hdhomerun_pkt.c
@@ -0,0 +1,245 @@
+/*
+ * hdhomerun_pkt.c
+ *
+ * Copyright © 2005-2006 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_pkt_t *hdhomerun_pkt_create(void)
+{
+ struct hdhomerun_pkt_t *pkt = (struct hdhomerun_pkt_t *)calloc(1, sizeof(struct hdhomerun_pkt_t));
+ if (!pkt) {
+ return NULL;
+ }
+
+ hdhomerun_pkt_reset(pkt);
+
+ return pkt;
+}
+
+void hdhomerun_pkt_destroy(struct hdhomerun_pkt_t *pkt)
+{
+ free(pkt);
+}
+
+void hdhomerun_pkt_reset(struct hdhomerun_pkt_t *pkt)
+{
+ pkt->limit = pkt->buffer + sizeof(pkt->buffer) - 4;
+ pkt->start = pkt->buffer + 1024;
+ pkt->end = pkt->start;
+ pkt->pos = pkt->start;
+}
+
+static uint32_t hdhomerun_pkt_calc_crc(uint8_t *start, uint8_t *end)
+{
+ uint8_t *pos = start;
+ uint32_t crc = 0xFFFFFFFF;
+ while (pos < end) {
+ uint8_t x = (uint8_t)(crc) ^ *pos++;
+ crc >>= 8;
+ if (x & 0x01) crc ^= 0x77073096;
+ if (x & 0x02) crc ^= 0xEE0E612C;
+ if (x & 0x04) crc ^= 0x076DC419;
+ if (x & 0x08) crc ^= 0x0EDB8832;
+ if (x & 0x10) crc ^= 0x1DB71064;
+ if (x & 0x20) crc ^= 0x3B6E20C8;
+ if (x & 0x40) crc ^= 0x76DC4190;
+ if (x & 0x80) crc ^= 0xEDB88320;
+ }
+ return crc ^ 0xFFFFFFFF;
+}
+
+uint8_t hdhomerun_pkt_read_u8(struct hdhomerun_pkt_t *pkt)
+{
+ uint8_t v = *pkt->pos++;
+ return v;
+}
+
+uint16_t hdhomerun_pkt_read_u16(struct hdhomerun_pkt_t *pkt)
+{
+ uint16_t v;
+ v = (uint16_t)*pkt->pos++ << 8;
+ v |= (uint16_t)*pkt->pos++ << 0;
+ return v;
+}
+
+uint32_t hdhomerun_pkt_read_u32(struct hdhomerun_pkt_t *pkt)
+{
+ uint32_t v;
+ v = (uint32_t)*pkt->pos++ << 24;
+ v |= (uint32_t)*pkt->pos++ << 16;
+ v |= (uint32_t)*pkt->pos++ << 8;
+ v |= (uint32_t)*pkt->pos++ << 0;
+ return v;
+}
+
+size_t hdhomerun_pkt_read_var_length(struct hdhomerun_pkt_t *pkt)
+{
+ size_t length;
+
+ if (pkt->pos + 1 > pkt->end) {
+ return (size_t)-1;
+ }
+
+ length = (size_t)*pkt->pos++;
+ if (length & 0x0080) {
+ if (pkt->pos + 1 > pkt->end) {
+ return (size_t)-1;
+ }
+
+ length &= 0x007F;
+ length |= (size_t)*pkt->pos++ << 7;
+ }
+
+ return length;
+}
+
+uint8_t *hdhomerun_pkt_read_tlv(struct hdhomerun_pkt_t *pkt, uint8_t *ptag, size_t *plength)
+{
+ if (pkt->pos + 2 > pkt->end) {
+ return NULL;
+ }
+
+ *ptag = hdhomerun_pkt_read_u8(pkt);
+ *plength = hdhomerun_pkt_read_var_length(pkt);
+
+ if (pkt->pos + *plength > pkt->end) {
+ return NULL;
+ }
+
+ return pkt->pos + *plength;
+}
+
+void hdhomerun_pkt_write_u8(struct hdhomerun_pkt_t *pkt, uint8_t v)
+{
+ *pkt->pos++ = v;
+
+ if (pkt->pos > pkt->end) {
+ pkt->end = pkt->pos;
+ }
+}
+
+void hdhomerun_pkt_write_u16(struct hdhomerun_pkt_t *pkt, uint16_t v)
+{
+ *pkt->pos++ = (uint8_t)(v >> 8);
+ *pkt->pos++ = (uint8_t)(v >> 0);
+
+ if (pkt->pos > pkt->end) {
+ pkt->end = pkt->pos;
+ }
+}
+
+void hdhomerun_pkt_write_u32(struct hdhomerun_pkt_t *pkt, uint32_t v)
+{
+ *pkt->pos++ = (uint8_t)(v >> 24);
+ *pkt->pos++ = (uint8_t)(v >> 16);
+ *pkt->pos++ = (uint8_t)(v >> 8);
+ *pkt->pos++ = (uint8_t)(v >> 0);
+
+ if (pkt->pos > pkt->end) {
+ pkt->end = pkt->pos;
+ }
+}
+
+void hdhomerun_pkt_write_var_length(struct hdhomerun_pkt_t *pkt, size_t v)
+{
+ if (v <= 127) {
+ *pkt->pos++ = (uint8_t)v;
+ } else {
+ *pkt->pos++ = (uint8_t)(v | 0x80);
+ *pkt->pos++ = (uint8_t)(v >> 7);
+ }
+
+ if (pkt->pos > pkt->end) {
+ pkt->end = pkt->pos;
+ }
+}
+
+void hdhomerun_pkt_write_mem(struct hdhomerun_pkt_t *pkt, const void *mem, size_t length)
+{
+ memcpy(pkt->pos, mem, length);
+ pkt->pos += length;
+
+ if (pkt->pos > pkt->end) {
+ pkt->end = pkt->pos;
+ }
+}
+
+int hdhomerun_pkt_open_frame(struct hdhomerun_pkt_t *pkt, uint16_t *ptype)
+{
+ pkt->pos = pkt->start;
+
+ if (pkt->pos + 4 > pkt->end) {
+ return 0;
+ }
+
+ *ptype = hdhomerun_pkt_read_u16(pkt);
+ size_t length = hdhomerun_pkt_read_u16(pkt);
+ pkt->pos += length;
+
+ if (pkt->pos + 4 > pkt->end) {
+ pkt->pos = pkt->start;
+ return 0;
+ }
+
+ uint32_t calc_crc = hdhomerun_pkt_calc_crc(pkt->start, pkt->pos);
+
+ uint32_t packet_crc;
+ packet_crc = (uint32_t)*pkt->pos++ << 0;
+ packet_crc |= (uint32_t)*pkt->pos++ << 8;
+ packet_crc |= (uint32_t)*pkt->pos++ << 16;
+ packet_crc |= (uint32_t)*pkt->pos++ << 24;
+ if (calc_crc != packet_crc) {
+ return -1;
+ }
+
+ pkt->start += 4;
+ pkt->end = pkt->start + length;
+ pkt->pos = pkt->start;
+ return 1;
+}
+
+void hdhomerun_pkt_seal_frame(struct hdhomerun_pkt_t *pkt, uint16_t frame_type)
+{
+ size_t length = pkt->end - pkt->start;
+
+ pkt->start -= 4;
+ pkt->pos = pkt->start;
+ hdhomerun_pkt_write_u16(pkt, frame_type);
+ hdhomerun_pkt_write_u16(pkt, (uint16_t)length);
+
+ uint32_t crc = hdhomerun_pkt_calc_crc(pkt->start, pkt->end);
+ *pkt->end++ = (uint8_t)(crc >> 0);
+ *pkt->end++ = (uint8_t)(crc >> 8);
+ *pkt->end++ = (uint8_t)(crc >> 16);
+ *pkt->end++ = (uint8_t)(crc >> 24);
+
+ pkt->pos = pkt->start;
+}
diff --git a/libhdhomerun/hdhomerun_pkt.h b/libhdhomerun/hdhomerun_pkt.h
new file mode 100755
index 0000000..5ac14c7
--- /dev/null
+++ b/libhdhomerun/hdhomerun_pkt.h
@@ -0,0 +1,178 @@
+/*
+ * hdhomerun_pkt.h
+ *
+ * Copyright © 2005-2006 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.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The discover protocol (UDP port 65001) and control protocol (TCP port 65001)
+ * both use the same packet based format:
+ * uint16_t Packet type
+ * uint16_t Payload length (bytes)
+ * uint8_t[] Payload data (0-n bytes).
+ * uint32_t CRC (Ethernet style 32-bit CRC)
+ *
+ * All variables are big-endian except for the crc which is little-endian.
+ *
+ * Valid values for the packet type are listed below as defines prefixed
+ * with "HDHOMERUN_TYPE_"
+ *
+ * Discovery:
+ *
+ * The payload for a discovery request or reply is a simple sequence of
+ * tag-length-value data:
+ * uint8_t Tag
+ * varlen Length
+ * uint8_t[] Value (0-n bytes)
+ *
+ * The length field can be one or two bytes long.
+ * For a length <= 127 bytes the length is expressed as a single byte. The
+ * most-significant-bit is clear indicating a single-byte length.
+ * For a length >= 128 bytes the length is expressed as a sequence of two bytes as follows:
+ * The first byte is contains the least-significant 7-bits of the length. The
+ * most-significant bit is then set (add 0x80) to indicate that it is a two byte length.
+ * The second byte contains the length shifted down 7 bits.
+ *
+ * A discovery request packet has a packet type of HDHOMERUN_TYPE_DISCOVER_REQ and should
+ * contain two tags: HDHOMERUN_TAG_DEVICE_TYPE and HDHOMERUN_TAG_DEVICE_ID.
+ * The HDHOMERUN_TAG_DEVICE_TYPE value should be set to HDHOMERUN_DEVICE_TYPE_TUNER.
+ * The HDHOMERUN_TAG_DEVICE_ID value should be set to HDHOMERUN_DEVICE_ID_WILDCARD to match
+ * all devices, or to the 32-bit device id number to match a single device.
+ *
+ * The discovery response packet has a packet type of HDHOMERUN_TYPE_DISCOVER_RPY and has the
+ * same format as the discovery request packet with the two tags: HDHOMERUN_TAG_DEVICE_TYPE and
+ * HDHOMERUN_TAG_DEVICE_ID. In the future additional tags may also be returned - unknown tags
+ * should be skipped and not treated as an error.
+ *
+ * Control get/set:
+ *
+ * The payload for a control get/set request is a simple sequence of tag-length-value data
+ * following the same format as for discover packets.
+ *
+ * A get request packet has a packet type of HDHOMERUN_TYPE_GETSET_REQ and should contain
+ * the tag: HDHOMERUN_TAG_GETSET_NAME. The HDHOMERUN_TAG_GETSET_NAME value should be a sequence
+ * of bytes forming a null-terminated string, including the NULL. The TLV length must include
+ * the NULL character so the length field should be set to strlen(str) + 1.
+ *
+ * A set request packet has a packet type of HDHOMERUN_TYPE_GETSET_REQ (same as a get request)
+ * and should contain two tags: HDHOMERUN_TAG_GETSET_NAME and HDHOMERUN_TAG_GETSET_VALUE.
+ * The HDHOMERUN_TAG_GETSET_NAME value should be a sequence of bytes forming a null-terminated
+ * string, including the NULL.
+ * The HDHOMERUN_TAG_GETSET_VALUE value should be a sequence of bytes forming a null-terminated
+ * string, including the NULL.
+ *
+ * The get and set reply packets have the packet type HDHOMERUN_TYPE_GETSET_RPY and have the same
+ * format as the set request packet with the two tags: HDHOMERUN_TAG_GETSET_NAME and
+ * HDHOMERUN_TAG_GETSET_VALUE. A set request is also implicit get request so the updated value is
+ * returned.
+ *
+ * If the device encounters an error handling the get or set request then the get/set reply packet
+ * will contain the tag HDHOMERUN_TAG_ERROR_MESSAGE. The format of the value is a sequence of
+ * bytes forming a null-terminated string, including the NULL.
+ *
+ * In the future additional tags may also be returned - unknown tags should be skipped and not
+ * treated as an error.
+ *
+ * Security note: The application should not rely on the NULL character being present. The
+ * application should write a NULL character based on the TLV length to protect the application
+ * from a potential attack.
+ *
+ * Firmware Upgrade:
+ *
+ * A firmware upgrade packet has a packet type of HDHOMERUN_TYPE_UPGRADE_REQ and has a fixed format:
+ * uint32_t Position in bytes from start of file.
+ * uint8_t[256] Firmware data (256 bytes)
+ *
+ * The data must be uploaded in 256 byte chunks and must be uploaded in order.
+ * The position number is in bytes so will increment by 256 each time.
+ *
+ * When all data is uploaded it should be signaled complete by sending another packet of type
+ * HDHOMERUN_TYPE_UPGRADE_REQ with payload of a single uint32_t with the value 0xFFFFFFFF.
+ */
+
+#define HDHOMERUN_DISCOVER_UDP_PORT 65001
+#define HDHOMERUN_CONTROL_TCP_PORT 65001
+
+#define HDHOMERUN_MAX_PACKET_SIZE 1460
+#define HDHOMERUN_MAX_PAYLOAD_SIZE 1452
+
+#define HDHOMERUN_TYPE_DISCOVER_REQ 0x0002
+#define HDHOMERUN_TYPE_DISCOVER_RPY 0x0003
+#define HDHOMERUN_TYPE_GETSET_REQ 0x0004
+#define HDHOMERUN_TYPE_GETSET_RPY 0x0005
+#define HDHOMERUN_TYPE_UPGRADE_REQ 0x0006
+#define HDHOMERUN_TYPE_UPGRADE_RPY 0x0007
+
+#define HDHOMERUN_TAG_DEVICE_TYPE 0x01
+#define HDHOMERUN_TAG_DEVICE_ID 0x02
+#define HDHOMERUN_TAG_GETSET_NAME 0x03
+#define HDHOMERUN_TAG_GETSET_VALUE 0x04
+#define HDHOMERUN_TAG_GETSET_LOCKKEY 0x15
+#define HDHOMERUN_TAG_ERROR_MESSAGE 0x05
+#define HDHOMERUN_TAG_TUNER_COUNT 0x10
+
+#define HDHOMERUN_DEVICE_TYPE_WILDCARD 0xFFFFFFFF
+#define HDHOMERUN_DEVICE_TYPE_TUNER 0x00000001
+#define HDHOMERUN_DEVICE_ID_WILDCARD 0xFFFFFFFF
+
+#define HDHOMERUN_MIN_PEEK_LENGTH 4
+
+struct hdhomerun_pkt_t {
+ uint8_t *pos;
+ uint8_t *start;
+ uint8_t *end;
+ uint8_t *limit;
+ uint8_t buffer[3074];
+};
+
+extern LIBTYPE struct hdhomerun_pkt_t *hdhomerun_pkt_create(void);
+extern LIBTYPE void hdhomerun_pkt_destroy(struct hdhomerun_pkt_t *pkt);
+extern LIBTYPE void hdhomerun_pkt_reset(struct hdhomerun_pkt_t *pkt);
+
+extern LIBTYPE uint8_t hdhomerun_pkt_read_u8(struct hdhomerun_pkt_t *pkt);
+extern LIBTYPE uint16_t hdhomerun_pkt_read_u16(struct hdhomerun_pkt_t *pkt);
+extern LIBTYPE uint32_t hdhomerun_pkt_read_u32(struct hdhomerun_pkt_t *pkt);
+extern LIBTYPE size_t hdhomerun_pkt_read_var_length(struct hdhomerun_pkt_t *pkt);
+extern LIBTYPE uint8_t *hdhomerun_pkt_read_tlv(struct hdhomerun_pkt_t *pkt, uint8_t *ptag, size_t *plength);
+
+extern LIBTYPE void hdhomerun_pkt_write_u8(struct hdhomerun_pkt_t *pkt, uint8_t v);
+extern LIBTYPE void hdhomerun_pkt_write_u16(struct hdhomerun_pkt_t *pkt, uint16_t v);
+extern LIBTYPE void hdhomerun_pkt_write_u32(struct hdhomerun_pkt_t *pkt, uint32_t v);
+extern LIBTYPE void hdhomerun_pkt_write_var_length(struct hdhomerun_pkt_t *pkt, size_t v);
+extern LIBTYPE void hdhomerun_pkt_write_mem(struct hdhomerun_pkt_t *pkt, const void *mem, size_t length);
+
+extern LIBTYPE bool_t hdhomerun_pkt_open_frame(struct hdhomerun_pkt_t *pkt, uint16_t *ptype);
+extern LIBTYPE void hdhomerun_pkt_seal_frame(struct hdhomerun_pkt_t *pkt, uint16_t frame_type);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_sock.h b/libhdhomerun/hdhomerun_sock.h
new file mode 100755
index 0000000..69e1919
--- /dev/null
+++ b/libhdhomerun/hdhomerun_sock.h
@@ -0,0 +1,71 @@
+/*
+ * hdhomerun_sock.h
+ *
+ * Copyright © 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.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct hdhomerun_local_ip_info_t {
+ uint32_t ip_addr;
+ uint32_t subnet_mask;
+};
+
+extern LIBTYPE int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count);
+
+#define HDHOMERUN_SOCK_INVALID -1
+
+typedef int hdhomerun_sock_t;
+
+extern LIBTYPE hdhomerun_sock_t hdhomerun_sock_create_udp(void);
+extern LIBTYPE hdhomerun_sock_t hdhomerun_sock_create_tcp(void);
+extern LIBTYPE void hdhomerun_sock_destroy(hdhomerun_sock_t sock);
+
+extern LIBTYPE int hdhomerun_sock_getlasterror(void);
+extern LIBTYPE uint32_t hdhomerun_sock_getsockname_addr(hdhomerun_sock_t sock);
+extern LIBTYPE uint16_t hdhomerun_sock_getsockname_port(hdhomerun_sock_t sock);
+extern LIBTYPE uint32_t hdhomerun_sock_getpeername_addr(hdhomerun_sock_t sock);
+extern LIBTYPE uint32_t hdhomerun_sock_getaddrinfo_addr(hdhomerun_sock_t sock, const char *name);
+
+extern LIBTYPE bool_t hdhomerun_sock_join_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip);
+extern LIBTYPE bool_t hdhomerun_sock_leave_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip);
+
+extern LIBTYPE bool_t hdhomerun_sock_bind(hdhomerun_sock_t sock, uint32_t local_addr, uint16_t local_port, bool_t allow_reuse);
+extern LIBTYPE bool_t hdhomerun_sock_connect(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout);
+
+extern LIBTYPE bool_t hdhomerun_sock_send(hdhomerun_sock_t sock, const void *data, size_t length, uint64_t timeout);
+extern LIBTYPE bool_t hdhomerun_sock_sendto(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout);
+
+extern LIBTYPE bool_t hdhomerun_sock_recv(hdhomerun_sock_t sock, void *data, size_t *length, uint64_t timeout);
+extern LIBTYPE bool_t hdhomerun_sock_recvfrom(hdhomerun_sock_t sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/hdhomerun_sock_posix.c b/libhdhomerun/hdhomerun_sock_posix.c
new file mode 100755
index 0000000..52dccc0
--- /dev/null
+++ b/libhdhomerun/hdhomerun_sock_posix.c
@@ -0,0 +1,514 @@
+/*
+ * hdhomerun_sock_posix.c
+ *
+ * Copyright © 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.
+ */
+
+/*
+ * Implementation notes:
+ *
+ * API specifies timeout for each operation (or zero for non-blocking).
+ *
+ * It is not possible to rely on the OS socket timeout as this will fail to
+ * detect the command-response situation where data is sent successfully and
+ * the other end chooses not to send a response (other than the TCP ack).
+ *
+ * The select() cannot be used with high socket numbers (typically max 1024)
+ * so the code works as follows:
+ * - Use non-blocking sockets to allow operation without select.
+ * - Use select where safe (low socket numbers).
+ * - Poll with short sleep when select cannot be used safely.
+ */
+
+#include "hdhomerun.h"
+
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+#ifndef SIOCGIFCONF
+#include <sys/sockio.h>
+#endif
+
+#ifndef _SIZEOF_ADDR_IFREQ
+#define _SIZEOF_ADDR_IFREQ(x) sizeof(x)
+#endif
+
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
+int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count)
+{
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock == HDHOMERUN_SOCK_INVALID) {
+ return -1;
+ }
+
+ struct ifconf ifc;
+ size_t ifreq_buffer_size = 1024;
+
+ while (1) {
+ ifc.ifc_len = ifreq_buffer_size;
+ ifc.ifc_buf = (char *)malloc(ifreq_buffer_size);
+ if (!ifc.ifc_buf) {
+ close(sock);
+ return -1;
+ }
+
+ memset(ifc.ifc_buf, 0, ifreq_buffer_size);
+
+ if (ioctl(sock, SIOCGIFCONF, &ifc) != 0) {
+ free(ifc.ifc_buf);
+ close(sock);
+ return -1;
+ }
+
+ if (ifc.ifc_len < ifreq_buffer_size) {
+ break;
+ }
+
+ free(ifc.ifc_buf);
+ ifreq_buffer_size += 1024;
+ }
+
+ char *ptr = ifc.ifc_buf;
+ char *end = ifc.ifc_buf + ifc.ifc_len;
+
+ int count = 0;
+ while (ptr <= end) {
+ struct ifreq *ifr = (struct ifreq *)ptr;
+ ptr += _SIZEOF_ADDR_IFREQ(*ifr);
+
+ if (ioctl(sock, SIOCGIFADDR, ifr) != 0) {
+ continue;
+ }
+
+ struct sockaddr_in *ip_addr_in = (struct sockaddr_in *)&(ifr->ifr_addr);
+ uint32_t ip_addr = ntohl(ip_addr_in->sin_addr.s_addr);
+ if (ip_addr == 0) {
+ continue;
+ }
+
+ if (ioctl(sock, SIOCGIFNETMASK, ifr) != 0) {
+ continue;
+ }
+
+ struct sockaddr_in *subnet_mask_in = (struct sockaddr_in *)&(ifr->ifr_addr);
+ uint32_t subnet_mask = ntohl(subnet_mask_in->sin_addr.s_addr);
+
+ struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[count++];
+ ip_info->ip_addr = ip_addr;
+ ip_info->subnet_mask = subnet_mask;
+
+ if (count >= max_count) {
+ break;
+ }
+ }
+
+ free(ifc.ifc_buf);
+ close(sock);
+ return count;
+}
+
+hdhomerun_sock_t hdhomerun_sock_create_udp(void)
+{
+ /* Create socket. */
+ hdhomerun_sock_t sock = (hdhomerun_sock_t)socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock == -1) {
+ return HDHOMERUN_SOCK_INVALID;
+ }
+
+ /* Set non-blocking */
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
+ close(sock);
+ return HDHOMERUN_SOCK_INVALID;
+ }
+
+ /* Allow broadcast. */
+ int sock_opt = 1;
+ setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&sock_opt, sizeof(sock_opt));
+
+ /* Success. */
+ return sock;
+}
+
+hdhomerun_sock_t hdhomerun_sock_create_tcp(void)
+{
+ /* Create socket. */
+ hdhomerun_sock_t sock = (hdhomerun_sock_t)socket(AF_INET, SOCK_STREAM, 0);
+ if (sock == -1) {
+ return HDHOMERUN_SOCK_INVALID;
+ }
+
+ /* Set non-blocking */
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
+ close(sock);
+ return HDHOMERUN_SOCK_INVALID;
+ }
+
+ /* Success. */
+ return sock;
+}
+
+void hdhomerun_sock_destroy(hdhomerun_sock_t sock)
+{
+ close(sock);
+}
+
+int hdhomerun_sock_getlasterror(void)
+{
+ return errno;
+}
+
+uint32_t hdhomerun_sock_getsockname_addr(hdhomerun_sock_t sock)
+{
+ struct sockaddr_in sock_addr;
+ socklen_t sockaddr_size = sizeof(sock_addr);
+
+ if (getsockname(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) {
+ return 0;
+ }
+
+ return ntohl(sock_addr.sin_addr.s_addr);
+}
+
+uint16_t hdhomerun_sock_getsockname_port(hdhomerun_sock_t sock)
+{
+ struct sockaddr_in sock_addr;
+ socklen_t sockaddr_size = sizeof(sock_addr);
+
+ if (getsockname(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) {
+ return 0;
+ }
+
+ return ntohs(sock_addr.sin_port);
+}
+
+uint32_t hdhomerun_sock_getpeername_addr(hdhomerun_sock_t sock)
+{
+ struct sockaddr_in sock_addr;
+ socklen_t sockaddr_size = sizeof(sock_addr);
+
+ if (getpeername(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) {
+ return 0;
+ }
+
+ return ntohl(sock_addr.sin_addr.s_addr);
+}
+
+uint32_t hdhomerun_sock_getaddrinfo_addr(hdhomerun_sock_t sock, const char *name)
+{
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ struct addrinfo *sock_info;
+ if (getaddrinfo(name, "", &hints, &sock_info) != 0) {
+ return 0;
+ }
+
+ struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr;
+ uint32_t addr = ntohl(sock_addr->sin_addr.s_addr);
+
+ freeaddrinfo(sock_info);
+ return addr;
+}
+
+bool_t hdhomerun_sock_join_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip)
+{
+ struct ip_mreq imr;
+ memset(&imr, 0, sizeof(imr));
+ imr.imr_multiaddr.s_addr = htonl(multicast_ip);
+ imr.imr_interface.s_addr = htonl(local_ip);
+
+ if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool_t hdhomerun_sock_leave_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip)
+{
+ struct ip_mreq imr;
+ memset(&imr, 0, sizeof(imr));
+ imr.imr_multiaddr.s_addr = htonl(multicast_ip);
+ imr.imr_interface.s_addr = htonl(local_ip);
+
+ if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool_t hdhomerun_sock_bind(hdhomerun_sock_t sock, uint32_t local_addr, uint16_t local_port, bool_t allow_reuse)
+{
+ int sock_opt = allow_reuse;
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt));
+
+ struct sockaddr_in sock_addr;
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_addr.s_addr = htonl(local_addr);
+ sock_addr.sin_port = htons(local_port);
+
+ if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool_t hdhomerun_sock_wait_for_read_event(hdhomerun_sock_t sock, uint64_t stop_time)
+{
+ uint64_t current_time = getcurrenttime();
+ if (current_time >= stop_time) {
+ return FALSE;
+ }
+
+ if (sock < FD_SETSIZE) {
+ uint64_t timeout = stop_time - current_time;
+ struct timeval t;
+ t.tv_sec = timeout / 1000;
+ t.tv_usec = (timeout % 1000) * 1000;
+
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(sock, &readfds);
+
+ fd_set errorfds;
+ FD_ZERO(&errorfds);
+ FD_SET(sock, &errorfds);
+
+ if (select(sock + 1, &readfds, NULL, &errorfds, &t) <= 0) {
+ return FALSE;
+ }
+ if (!FD_ISSET(sock, &readfds)) {
+ return FALSE;
+ }
+ } else {
+ uint64_t delay = stop_time - current_time;
+ if (delay > 5) {
+ delay = 5;
+ }
+
+ msleep_approx(delay);
+ }
+
+ return TRUE;
+}
+
+static bool_t hdhomerun_sock_wait_for_write_event(hdhomerun_sock_t sock, uint64_t stop_time)
+{
+ uint64_t current_time = getcurrenttime();
+ if (current_time >= stop_time) {
+ return FALSE;
+ }
+
+ if (sock < FD_SETSIZE) {
+ uint64_t timeout = stop_time - current_time;
+ struct timeval t;
+ t.tv_sec = timeout / 1000;
+ t.tv_usec = (timeout % 1000) * 1000;
+
+ fd_set writefds;
+ FD_ZERO(&writefds);
+ FD_SET(sock, &writefds);
+
+ fd_set errorfds;
+ FD_ZERO(&errorfds);
+ FD_SET(sock, &errorfds);
+
+ if (select(sock + 1, NULL, &writefds, &errorfds, &t) <= 0) {
+ return FALSE;
+ }
+ if (!FD_ISSET(sock, &writefds)) {
+ return FALSE;
+ }
+ } else {
+ uint64_t delay = stop_time - current_time;
+ if (delay > 5) {
+ delay = 5;
+ }
+
+ msleep_approx(delay);
+ }
+
+ return TRUE;
+}
+
+bool_t hdhomerun_sock_connect(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout)
+{
+ struct sockaddr_in sock_addr;
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_addr.s_addr = htonl(remote_addr);
+ sock_addr.sin_port = htons(remote_port);
+
+ if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) {
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
+ return FALSE;
+ }
+ }
+
+ uint64_t stop_time = getcurrenttime() + timeout;
+
+ while (1) {
+ if (send(sock, NULL, 0, MSG_NOSIGNAL) == 0) {
+ return TRUE;
+ }
+
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS) && (errno != ENOTCONN)) {
+ return FALSE;
+ }
+
+ if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+ return FALSE;
+ }
+ }
+}
+
+bool_t hdhomerun_sock_send(hdhomerun_sock_t sock, const void *data, size_t length, uint64_t timeout)
+{
+ uint64_t stop_time = getcurrenttime() + timeout;
+ const uint8_t *ptr = (const uint8_t *)data;
+
+ while (1) {
+ int ret = send(sock, ptr, length, MSG_NOSIGNAL);
+ if (ret <= 0) {
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
+ return FALSE;
+ }
+ if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+ return FALSE;
+ }
+ continue;
+ }
+
+ if (ret < (int)length) {
+ ptr += ret;
+ length -= ret;
+ continue;
+ }
+
+ return TRUE;
+ }
+}
+
+bool_t hdhomerun_sock_sendto(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout)
+{
+ uint64_t stop_time = getcurrenttime() + timeout;
+ const uint8_t *ptr = (const uint8_t *)data;
+
+ while (1) {
+ struct sockaddr_in sock_addr;
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_addr.s_addr = htonl(remote_addr);
+ sock_addr.sin_port = htons(remote_port);
+
+ int ret = sendto(sock, ptr, length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr));
+ if (ret <= 0) {
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
+ return FALSE;
+ }
+ if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+ return FALSE;
+ }
+ continue;
+ }
+
+ if (ret < (int)length) {
+ ptr += ret;
+ length -= ret;
+ continue;
+ }
+
+ return TRUE;
+ }
+}
+
+bool_t hdhomerun_sock_recv(hdhomerun_sock_t sock, void *data, size_t *length, uint64_t timeout)
+{
+ uint64_t stop_time = getcurrenttime() + timeout;
+
+ while (1) {
+ int ret = recv(sock, data, *length, 0);
+ if (ret < 0) {
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
+ return FALSE;
+ }
+ if (!hdhomerun_sock_wait_for_read_event(sock, stop_time)) {
+ return FALSE;
+ }
+ continue;
+ }
+
+ if (ret == 0) {
+ return FALSE;
+ }
+
+ *length = ret;
+ return TRUE;
+ }
+}
+
+bool_t hdhomerun_sock_recvfrom(hdhomerun_sock_t sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout)
+{
+ uint64_t stop_time = getcurrenttime() + timeout;
+
+ while (1) {
+ struct sockaddr_in sock_addr;
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ socklen_t sockaddr_size = sizeof(sock_addr);
+
+ int ret = recvfrom(sock, data, *length, 0, (struct sockaddr *)&sock_addr, &sockaddr_size);
+ if (ret < 0) {
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
+ return FALSE;
+ }
+ if (!hdhomerun_sock_wait_for_read_event(sock, stop_time)) {
+ return FALSE;
+ }
+ continue;
+ }
+
+ if (ret == 0) {
+ return FALSE;
+ }
+
+ *remote_addr = ntohl(sock_addr.sin_addr.s_addr);
+ *remote_port = ntohs(sock_addr.sin_port);
+ *length = ret;
+ return TRUE;
+ }
+}
diff --git a/libhdhomerun/hdhomerun_sock_windows.c b/libhdhomerun/hdhomerun_sock_windows.c
new file mode 100755
index 0000000..c599694
--- /dev/null
+++ b/libhdhomerun/hdhomerun_sock_windows.c
@@ -0,0 +1,463 @@
+/*
+ * hdhomerun_sock_windows.c
+ *
+ * Copyright © 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.
+ */
+
+/*
+ * Implementation notes:
+ *
+ * API specifies timeout for each operation (or zero for non-blocking).
+ *
+ * It is not possible to rely on the OS socket timeout as this will fail to
+ * detect the command-response situation where data is sent successfully and
+ * the other end chooses not to send a response (other than the TCP ack).
+ *
+ * Windows supports select() however native WSA events are used to:
+ * - avoid problems with socket numbers above 1024.
+ * - wait without allowing other events handlers to run (important for use
+ * with win7 WMC).
+ */
+
+#include "hdhomerun.h"
+#include <windows.h>
+#include <iphlpapi.h>
+
+int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count)
+{
+ PIP_ADAPTER_INFO AdapterInfo;
+ ULONG AdapterInfoLength = sizeof(IP_ADAPTER_INFO) * 16;
+
+ while (1) {
+ AdapterInfo = (IP_ADAPTER_INFO *)malloc(AdapterInfoLength);
+ if (!AdapterInfo) {
+ return -1;
+ }
+
+ ULONG LengthNeeded = AdapterInfoLength;
+ DWORD Ret = GetAdaptersInfo(AdapterInfo, &LengthNeeded);
+ if (Ret == NO_ERROR) {
+ break;
+ }
+
+ free(AdapterInfo);
+
+ if (Ret != ERROR_BUFFER_OVERFLOW) {
+ return -1;
+ }
+ if (AdapterInfoLength >= LengthNeeded) {
+ return -1;
+ }
+
+ AdapterInfoLength = LengthNeeded;
+ }
+
+ int count = 0;
+ PIP_ADAPTER_INFO Adapter = AdapterInfo;
+ while (Adapter) {
+ IP_ADDR_STRING *IPAddr = &Adapter->IpAddressList;
+ while (IPAddr) {
+ uint32_t ip_addr = ntohl(inet_addr(IPAddr->IpAddress.String));
+ uint32_t subnet_mask = ntohl(inet_addr(IPAddr->IpMask.String));
+
+ if (ip_addr == 0) {
+ IPAddr = IPAddr->Next;
+ continue;
+ }
+
+ struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[count++];
+ ip_info->ip_addr = ip_addr;
+ ip_info->subnet_mask = subnet_mask;
+
+ if (count >= max_count) {
+ break;
+ }
+
+ IPAddr = IPAddr->Next;
+ }
+
+ if (count >= max_count) {
+ break;
+ }
+
+ Adapter = Adapter->Next;
+ }
+
+ free(AdapterInfo);
+ return count;
+}
+
+hdhomerun_sock_t hdhomerun_sock_create_udp(void)
+{
+ /* Create socket. */
+ hdhomerun_sock_t sock = (hdhomerun_sock_t)socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock == -1) {
+ return HDHOMERUN_SOCK_INVALID;
+ }
+
+ /* Set non-blocking */
+ unsigned long mode = 1;
+ if (ioctlsocket(sock, FIONBIO, &mode) != 0) {
+ closesocket(sock);
+ return HDHOMERUN_SOCK_INVALID;
+ }
+
+ /* Allow broadcast. */
+ int sock_opt = 1;
+ setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&sock_opt, sizeof(sock_opt));
+
+ /* Success. */
+ return sock;
+}
+
+hdhomerun_sock_t hdhomerun_sock_create_tcp(void)
+{
+ /* Create socket. */
+ hdhomerun_sock_t sock = (hdhomerun_sock_t)socket(AF_INET, SOCK_STREAM, 0);
+ if (sock == -1) {
+ return HDHOMERUN_SOCK_INVALID;
+ }
+
+ /* Set non-blocking */
+ unsigned long mode = 1;
+ if (ioctlsocket(sock, FIONBIO, &mode) != 0) {
+ closesocket(sock);
+ return HDHOMERUN_SOCK_INVALID;
+ }
+
+ /* Success. */
+ return sock;
+}
+
+void hdhomerun_sock_destroy(hdhomerun_sock_t sock)
+{
+ closesocket(sock);
+}
+
+int hdhomerun_sock_getlasterror(void)
+{
+ return WSAGetLastError();
+}
+
+uint32_t hdhomerun_sock_getsockname_addr(hdhomerun_sock_t sock)
+{
+ struct sockaddr_in sock_addr;
+ int sockaddr_size = sizeof(sock_addr);
+
+ if (getsockname(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) {
+ return 0;
+ }
+
+ return ntohl(sock_addr.sin_addr.s_addr);
+}
+
+uint16_t hdhomerun_sock_getsockname_port(hdhomerun_sock_t sock)
+{
+ struct sockaddr_in sock_addr;
+ int sockaddr_size = sizeof(sock_addr);
+
+ if (getsockname(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) {
+ return 0;
+ }
+
+ return ntohs(sock_addr.sin_port);
+}
+
+uint32_t hdhomerun_sock_getpeername_addr(hdhomerun_sock_t sock)
+{
+ struct sockaddr_in sock_addr;
+ int sockaddr_size = sizeof(sock_addr);
+
+ if (getpeername(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) {
+ return 0;
+ }
+
+ return ntohl(sock_addr.sin_addr.s_addr);
+}
+
+uint32_t hdhomerun_sock_getaddrinfo_addr(hdhomerun_sock_t sock, const char *name)
+{
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ struct addrinfo *sock_info;
+ if (getaddrinfo(name, "", &hints, &sock_info) != 0) {
+ return 0;
+ }
+
+ struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr;
+ uint32_t addr = ntohl(sock_addr->sin_addr.s_addr);
+
+ freeaddrinfo(sock_info);
+ return addr;
+}
+
+bool_t hdhomerun_sock_join_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip)
+{
+ struct ip_mreq imr;
+ memset(&imr, 0, sizeof(imr));
+ imr.imr_multiaddr.s_addr = htonl(multicast_ip);
+ imr.imr_interface.s_addr = htonl(local_ip);
+
+ if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool_t hdhomerun_sock_leave_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip)
+{
+ struct ip_mreq imr;
+ memset(&imr, 0, sizeof(imr));
+ imr.imr_multiaddr.s_addr = htonl(multicast_ip);
+ imr.imr_interface.s_addr = htonl(local_ip);
+
+ if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool_t hdhomerun_sock_bind(hdhomerun_sock_t sock, uint32_t local_addr, uint16_t local_port, bool_t allow_reuse)
+{
+ int sock_opt = allow_reuse;
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt));
+
+ struct sockaddr_in sock_addr;
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_addr.s_addr = htonl(local_addr);
+ sock_addr.sin_port = htons(local_port);
+
+ if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool_t hdhomerun_sock_connect(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout)
+{
+ /* Connect (non-blocking). */
+ struct sockaddr_in sock_addr;
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_addr.s_addr = htonl(remote_addr);
+ sock_addr.sin_port = htons(remote_port);
+
+ if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) {
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ return FALSE;
+ }
+ }
+
+ /* Wait for connect to complete (both success and failure will signal). */
+ WSAEVENT wsa_event = WSACreateEvent();
+ if (wsa_event == WSA_INVALID_EVENT) {
+ return FALSE;
+ }
+
+ if (WSAEventSelect(sock, wsa_event, FD_WRITE | FD_CLOSE) == SOCKET_ERROR) {
+ WSACloseEvent(wsa_event);
+ return FALSE;
+ }
+
+ DWORD ret = WaitForSingleObjectEx(wsa_event, (DWORD)timeout, FALSE);
+ WSACloseEvent(wsa_event);
+ if (ret != WAIT_OBJECT_0) {
+ return FALSE;
+ }
+
+ /* Detect success/failure. */
+ wsa_event = WSACreateEvent();
+ if (wsa_event == WSA_INVALID_EVENT) {
+ return FALSE;
+ }
+
+ if (WSAEventSelect(sock, wsa_event, FD_CLOSE) == SOCKET_ERROR) {
+ WSACloseEvent(wsa_event);
+ return FALSE;
+ }
+
+ ret = WaitForSingleObjectEx(wsa_event, 0, FALSE);
+ WSACloseEvent(wsa_event);
+ if (ret == WAIT_OBJECT_0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool_t hdhomerun_sock_wait_for_event(hdhomerun_sock_t sock, long event_type, uint64_t stop_time)
+{
+ uint64_t current_time = getcurrenttime();
+ if (current_time >= stop_time) {
+ return FALSE;
+ }
+
+ WSAEVENT wsa_event = WSACreateEvent();
+ if (wsa_event == WSA_INVALID_EVENT) {
+ return FALSE;
+ }
+
+ if (WSAEventSelect(sock, wsa_event, event_type) == SOCKET_ERROR) {
+ WSACloseEvent(wsa_event);
+ return FALSE;
+ }
+
+ DWORD ret = WaitForSingleObjectEx(wsa_event, (DWORD)(stop_time - current_time), FALSE);
+ WSACloseEvent(wsa_event);
+
+ if (ret != WAIT_OBJECT_0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool_t hdhomerun_sock_send(hdhomerun_sock_t sock, const void *data, size_t length, uint64_t timeout)
+{
+ uint64_t stop_time = getcurrenttime() + timeout;
+ const uint8_t *ptr = (uint8_t *)data;
+
+ while (1) {
+ int ret = send(sock, (char *)ptr, (int)length, 0);
+ if (ret <= 0) {
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ return FALSE;
+ }
+ if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) {
+ return FALSE;
+ }
+ continue;
+ }
+
+ if (ret < (int)length) {
+ ptr += ret;
+ length -= ret;
+ continue;
+ }
+
+ return TRUE;
+ }
+}
+
+bool_t hdhomerun_sock_sendto(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout)
+{
+ uint64_t stop_time = getcurrenttime() + timeout;
+ const uint8_t *ptr = (uint8_t *)data;
+
+ while (1) {
+ struct sockaddr_in sock_addr;
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_addr.s_addr = htonl(remote_addr);
+ sock_addr.sin_port = htons(remote_port);
+
+ int ret = sendto(sock, (char *)ptr, (int)length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr));
+ if (ret <= 0) {
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ return FALSE;
+ }
+ if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) {
+ return FALSE;
+ }
+ continue;
+ }
+
+ if (ret < (int)length) {
+ ptr += ret;
+ length -= ret;
+ continue;
+ }
+
+ return TRUE;
+ }
+}
+
+bool_t hdhomerun_sock_recv(hdhomerun_sock_t sock, void *data, size_t *length, uint64_t timeout)
+{
+ uint64_t stop_time = getcurrenttime() + timeout;
+
+ while (1) {
+ int ret = recv(sock, (char *)data, (int)(*length), 0);
+ if (ret < 0) {
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ return FALSE;
+ }
+ if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) {
+ return FALSE;
+ }
+ continue;
+ }
+
+ if (ret == 0) {
+ return FALSE;
+ }
+
+ *length = ret;
+ return TRUE;
+ }
+}
+
+bool_t hdhomerun_sock_recvfrom(hdhomerun_sock_t sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout)
+{
+ uint64_t stop_time = getcurrenttime() + timeout;
+
+ while (1) {
+ struct sockaddr_in sock_addr;
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ int sockaddr_size = sizeof(sock_addr);
+
+ int ret = recvfrom(sock, (char *)data, (int)(*length), 0, (struct sockaddr *)&sock_addr, &sockaddr_size);
+ if (ret < 0) {
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ return FALSE;
+ }
+ if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) {
+ return FALSE;
+ }
+ continue;
+ }
+
+ if (ret == 0) {
+ return FALSE;
+ }
+
+ *remote_addr = ntohl(sock_addr.sin_addr.s_addr);
+ *remote_port = ntohs(sock_addr.sin_port);
+ *length = ret;
+ return TRUE;
+ }
+}
diff --git a/libhdhomerun/hdhomerun_types.h b/libhdhomerun/hdhomerun_types.h
new file mode 100755
index 0000000..dd0eb1c
--- /dev/null
+++ b/libhdhomerun/hdhomerun_types.h
@@ -0,0 +1,90 @@
+/*
+ * hdhomerun_types.h
+ *
+ * Copyright © 2008-2009 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.
+ */
+
+#define HDHOMERUN_STATUS_COLOR_NEUTRAL 0xFFFFFFFF
+#define HDHOMERUN_STATUS_COLOR_RED 0xFFFF0000
+#define HDHOMERUN_STATUS_COLOR_YELLOW 0xFFFFFF00
+#define HDHOMERUN_STATUS_COLOR_GREEN 0xFF00C000
+
+struct hdhomerun_device_t;
+struct hdhomerun_device_allocation_t;
+
+struct hdhomerun_tuner_status_t {
+ char channel[32];
+ char lock_str[32];
+ bool_t signal_present;
+ bool_t lock_supported;
+ bool_t lock_unsupported;
+ unsigned int signal_strength;
+ unsigned int signal_to_noise_quality;
+ unsigned int symbol_error_quality;
+ uint32_t raw_bits_per_second;
+ uint32_t packets_per_second;
+};
+
+struct hdhomerun_tuner_vstatus_t {
+ char vchannel[32];
+ char name[32];
+ char auth[32];
+ char cci[32];
+ char cgms[32];
+ bool_t not_subscribed;
+ bool_t not_available;
+ bool_t copy_protected;
+};
+
+struct hdhomerun_channelscan_program_t {
+ char program_str[64];
+ uint16_t program_number;
+ uint16_t virtual_major;
+ uint16_t virtual_minor;
+ uint16_t type;
+ char name[32];
+};
+
+#define HDHOMERUN_CHANNELSCAN_MAX_PROGRAM_COUNT 64
+
+struct hdhomerun_channelscan_result_t {
+ char channel_str[64];
+ uint32_t channelmap;
+ uint32_t frequency;
+ struct hdhomerun_tuner_status_t status;
+ int program_count;
+ struct hdhomerun_channelscan_program_t programs[HDHOMERUN_CHANNELSCAN_MAX_PROGRAM_COUNT];
+ bool_t transport_stream_id_detected;
+ uint16_t transport_stream_id;
+};
+
+struct hdhomerun_plotsample_t {
+ int16_t real;
+ int16_t imag;
+};
diff --git a/libhdhomerun/hdhomerun_video.c b/libhdhomerun/hdhomerun_video.c
new file mode 100755
index 0000000..52b8dca
--- /dev/null
+++ b/libhdhomerun/hdhomerun_video.c
@@ -0,0 +1,395 @@
+/*
+ * hdhomerun_video.c
+ *
+ * Copyright © 2006-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_video_sock_t {
+ pthread_mutex_t lock;
+ struct hdhomerun_debug_t *dbg;
+ hdhomerun_sock_t sock;
+
+ volatile size_t head;
+ volatile size_t tail;
+ uint8_t *buffer;
+ size_t buffer_size;
+ size_t advance;
+
+ pthread_t thread;
+ volatile bool_t terminate;
+
+ volatile uint32_t packet_count;
+ volatile uint32_t transport_error_count;
+ volatile uint32_t network_error_count;
+ volatile uint32_t sequence_error_count;
+ volatile uint32_t overflow_error_count;
+
+ volatile uint32_t rtp_sequence;
+ volatile uint8_t sequence[0x2000];
+};
+
+static THREAD_FUNC_PREFIX hdhomerun_video_thread_execute(void *arg);
+
+struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool_t allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg)
+{
+ /* Create object. */
+ struct hdhomerun_video_sock_t *vs = (struct hdhomerun_video_sock_t *)calloc(1, sizeof(struct hdhomerun_video_sock_t));
+ if (!vs) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to allocate video object\n");
+ return NULL;
+ }
+
+ vs->dbg = dbg;
+ vs->sock = HDHOMERUN_SOCK_INVALID;
+ pthread_mutex_init(&vs->lock, NULL);
+
+ /* Reset sequence tracking. */
+ hdhomerun_video_flush(vs);
+
+ /* Buffer size. */
+ vs->buffer_size = (buffer_size / VIDEO_DATA_PACKET_SIZE) * VIDEO_DATA_PACKET_SIZE;
+ if (vs->buffer_size == 0) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_video_create: invalid buffer size (%lu bytes)\n", (unsigned long)buffer_size);
+ goto error;
+ }
+ vs->buffer_size += VIDEO_DATA_PACKET_SIZE;
+
+ /* Create buffer. */
+ vs->buffer = (uint8_t *)malloc(vs->buffer_size);
+ if (!vs->buffer) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to allocate buffer (%lu bytes)\n", (unsigned long)vs->buffer_size);
+ goto error;
+ }
+
+ /* Create socket. */
+ vs->sock = hdhomerun_sock_create_udp();
+ if (vs->sock == HDHOMERUN_SOCK_INVALID) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to allocate socket\n");
+ goto error;
+ }
+
+ /* Expand socket buffer size. */
+ int rx_size = 1024 * 1024;
+ setsockopt(vs->sock, SOL_SOCKET, SO_RCVBUF, (char *)&rx_size, sizeof(rx_size));
+
+ /* Bind socket. */
+ if (!hdhomerun_sock_bind(vs->sock, INADDR_ANY, listen_port, allow_port_reuse)) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to bind socket (port %u)\n", listen_port);
+ goto error;
+ }
+
+ /* Start thread. */
+ if (pthread_create(&vs->thread, NULL, &hdhomerun_video_thread_execute, vs) != 0) {
+ hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to start thread\n");
+ goto error;
+ }
+
+ /* Success. */
+ return vs;
+
+error:
+ if (vs->sock != HDHOMERUN_SOCK_INVALID) {
+ hdhomerun_sock_destroy(vs->sock);
+ }
+ if (vs->buffer) {
+ free(vs->buffer);
+ }
+ free(vs);
+ return NULL;
+}
+
+void hdhomerun_video_destroy(struct hdhomerun_video_sock_t *vs)
+{
+ vs->terminate = TRUE;
+ pthread_join(vs->thread, NULL);
+
+ hdhomerun_sock_destroy(vs->sock);
+ free(vs->buffer);
+
+ free(vs);
+}
+
+hdhomerun_sock_t hdhomerun_video_get_sock(struct hdhomerun_video_sock_t *vs)
+{
+ return vs->sock;
+}
+
+uint16_t hdhomerun_video_get_local_port(struct hdhomerun_video_sock_t *vs)
+{
+ uint16_t port = hdhomerun_sock_getsockname_port(vs->sock);
+ if (port == 0) {
+ hdhomerun_debug_printf(vs->dbg, "hdhomerun_video_get_local_port: getsockname failed (%d)\n", hdhomerun_sock_getlasterror());
+ return 0;
+ }
+
+ return port;
+}
+
+int hdhomerun_video_join_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip)
+{
+ if (!hdhomerun_sock_join_multicast_group(vs->sock, multicast_ip, local_ip)) {
+ hdhomerun_debug_printf(vs->dbg, "hdhomerun_video_join_multicast_group: setsockopt failed (%d)\n", hdhomerun_sock_getlasterror());
+ return -1;
+ }
+
+ return 1;
+}
+
+void hdhomerun_video_leave_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip)
+{
+ if (!hdhomerun_sock_leave_multicast_group(vs->sock, multicast_ip, local_ip)) {
+ hdhomerun_debug_printf(vs->dbg, "hdhomerun_video_leave_multicast_group: setsockopt failed (%d)\n", hdhomerun_sock_getlasterror());
+ }
+}
+
+static void hdhomerun_video_stats_ts_pkt(struct hdhomerun_video_sock_t *vs, uint8_t *ptr)
+{
+ uint16_t packet_identifier = ((uint16_t)(ptr[1] & 0x1F) << 8) | (uint16_t)ptr[2];
+ if (packet_identifier == 0x1FFF) {
+ return;
+ }
+
+ bool_t transport_error = ptr[1] >> 7;
+ if (transport_error) {
+ vs->transport_error_count++;
+ vs->sequence[packet_identifier] = 0xFF;
+ return;
+ }
+
+ uint8_t sequence = ptr[3] & 0x0F;
+
+ uint8_t previous_sequence = vs->sequence[packet_identifier];
+ vs->sequence[packet_identifier] = sequence;
+
+ if (previous_sequence == 0xFF) {
+ return;
+ }
+ if (sequence == ((previous_sequence + 1) & 0x0F)) {
+ return;
+ }
+ if (sequence == previous_sequence) {
+ return;
+ }
+
+ vs->sequence_error_count++;
+}
+
+static void hdhomerun_video_parse_rtp(struct hdhomerun_video_sock_t *vs, struct hdhomerun_pkt_t *pkt)
+{
+ pkt->pos += 2;
+ uint32_t rtp_sequence = hdhomerun_pkt_read_u16(pkt);
+ pkt->pos += 8;
+
+ uint32_t previous_rtp_sequence = vs->rtp_sequence;
+ vs->rtp_sequence = rtp_sequence;
+
+ /* Initial case - first packet received. */
+ if (previous_rtp_sequence == 0xFFFFFFFF) {
+ return;
+ }
+
+ /* Normal case - next sequence number. */
+ if (rtp_sequence == ((previous_rtp_sequence + 1) & 0xFFFF)) {
+ return;
+ }
+
+ /* Error case - sequence missed. */
+ vs->network_error_count++;
+
+ /* Restart pid sequence check after packet loss. */
+ int i;
+ for (i = 0; i < 0x2000; i++) {
+ vs->sequence[i] = 0xFF;
+ }
+}
+
+static THREAD_FUNC_PREFIX hdhomerun_video_thread_execute(void *arg)
+{
+ struct hdhomerun_video_sock_t *vs = (struct hdhomerun_video_sock_t *)arg;
+ struct hdhomerun_pkt_t pkt_inst;
+
+ while (!vs->terminate) {
+ struct hdhomerun_pkt_t *pkt = &pkt_inst;
+ hdhomerun_pkt_reset(pkt);
+
+ /* Receive. */
+ size_t length = VIDEO_RTP_DATA_PACKET_SIZE;
+ if (!hdhomerun_sock_recv(vs->sock, pkt->end, &length, 25)) {
+ continue;
+ }
+
+ pkt->end += length;
+
+ if (length == VIDEO_RTP_DATA_PACKET_SIZE) {
+ hdhomerun_video_parse_rtp(vs, pkt);
+ length = (int)(pkt->end - pkt->pos);
+ }
+
+ if (length != VIDEO_DATA_PACKET_SIZE) {
+ /* Data received but not valid - ignore. */
+ continue;
+ }
+
+ pthread_mutex_lock(&vs->lock);
+
+ /* Store in ring buffer. */
+ size_t head = vs->head;
+ uint8_t *ptr = vs->buffer + head;
+ memcpy(ptr, pkt->pos, length);
+
+ /* Stats. */
+ vs->packet_count++;
+ hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 0);
+ hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 1);
+ hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 2);
+ hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 3);
+ hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 4);
+ hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 5);
+ hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 6);
+
+ /* Calculate new head. */
+ head += length;
+ if (head >= vs->buffer_size) {
+ head -= vs->buffer_size;
+ }
+
+ /* Check for buffer overflow. */
+ if (head == vs->tail) {
+ vs->overflow_error_count++;
+ pthread_mutex_unlock(&vs->lock);
+ continue;
+ }
+
+ vs->head = head;
+
+ pthread_mutex_unlock(&vs->lock);
+ }
+
+ return NULL;
+}
+
+uint8_t *hdhomerun_video_recv(struct hdhomerun_video_sock_t *vs, size_t max_size, size_t *pactual_size)
+{
+ pthread_mutex_lock(&vs->lock);
+
+ size_t head = vs->head;
+ size_t tail = vs->tail;
+
+ if (vs->advance > 0) {
+ tail += vs->advance;
+ if (tail >= vs->buffer_size) {
+ tail -= vs->buffer_size;
+ }
+
+ vs->tail = tail;
+ }
+
+ if (head == tail) {
+ vs->advance = 0;
+ *pactual_size = 0;
+ pthread_mutex_unlock(&vs->lock);
+ return NULL;
+ }
+
+ size_t size = (max_size / VIDEO_DATA_PACKET_SIZE) * VIDEO_DATA_PACKET_SIZE;
+ if (size == 0) {
+ vs->advance = 0;
+ *pactual_size = 0;
+ pthread_mutex_unlock(&vs->lock);
+ return NULL;
+ }
+
+ size_t avail;
+ if (head > tail) {
+ avail = head - tail;
+ } else {
+ avail = vs->buffer_size - tail;
+ }
+ if (size > avail) {
+ size = avail;
+ }
+ vs->advance = size;
+ *pactual_size = size;
+ uint8_t *result = vs->buffer + tail;
+
+ pthread_mutex_unlock(&vs->lock);
+ return result;
+}
+
+void hdhomerun_video_flush(struct hdhomerun_video_sock_t *vs)
+{
+ pthread_mutex_lock(&vs->lock);
+
+ vs->tail = vs->head;
+ vs->advance = 0;
+
+ vs->rtp_sequence = 0xFFFFFFFF;
+
+ int i;
+ for (i = 0; i < 0x2000; i++) {
+ vs->sequence[i] = 0xFF;
+ }
+
+ vs->packet_count = 0;
+ vs->transport_error_count = 0;
+ vs->network_error_count = 0;
+ vs->sequence_error_count = 0;
+ vs->overflow_error_count = 0;
+
+ pthread_mutex_unlock(&vs->lock);
+}
+
+void hdhomerun_video_debug_print_stats(struct hdhomerun_video_sock_t *vs)
+{
+ struct hdhomerun_video_stats_t stats;
+ hdhomerun_video_get_stats(vs, &stats);
+
+ hdhomerun_debug_printf(vs->dbg, "video sock: pkt=%lu net=%lu te=%lu miss=%lu drop=%lu\n",
+ (unsigned long)stats.packet_count, (unsigned long)stats.network_error_count,
+ (unsigned long)stats.transport_error_count, (unsigned long)stats.sequence_error_count,
+ (unsigned long)stats.overflow_error_count
+ );
+}
+
+void hdhomerun_video_get_stats(struct hdhomerun_video_sock_t *vs, struct hdhomerun_video_stats_t *stats)
+{
+ memset(stats, 0, sizeof(struct hdhomerun_video_stats_t));
+
+ pthread_mutex_lock(&vs->lock);
+
+ stats->packet_count = vs->packet_count;
+ stats->network_error_count = vs->network_error_count;
+ stats->transport_error_count = vs->transport_error_count;
+ stats->sequence_error_count = vs->sequence_error_count;
+ stats->overflow_error_count = vs->overflow_error_count;
+
+ pthread_mutex_unlock(&vs->lock);
+}
diff --git a/libhdhomerun/hdhomerun_video.h b/libhdhomerun/hdhomerun_video.h
new file mode 100755
index 0000000..90dc9d8
--- /dev/null
+++ b/libhdhomerun/hdhomerun_video.h
@@ -0,0 +1,116 @@
+/*
+ * hdhomerun_video.h
+ *
+ * Copyright © 2006 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.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct hdhomerun_video_sock_t;
+
+struct hdhomerun_video_stats_t {
+ uint32_t packet_count;
+ uint32_t network_error_count;
+ uint32_t transport_error_count;
+ uint32_t sequence_error_count;
+ uint32_t overflow_error_count;
+};
+
+#define TS_PACKET_SIZE 188
+#define VIDEO_DATA_PACKET_SIZE (188 * 7)
+#define VIDEO_DATA_BUFFER_SIZE_1S (20000000 / 8)
+
+#define VIDEO_RTP_DATA_PACKET_SIZE ((188 * 7) + 12)
+
+/*
+ * Create a video/data socket.
+ *
+ * uint16_t listen_port: Port number to listen on. Set to 0 to auto-select.
+ * size_t buffer_size: Size of receive buffer. For 1 second of buffer use VIDEO_DATA_BUFFER_SIZE_1S.
+ * struct hdhomerun_debug_t *dbg: Pointer to debug logging object. May be NULL.
+ *
+ * Returns a pointer to the newly created control socket.
+ *
+ * When no longer needed, the socket should be destroyed by calling hdhomerun_control_destroy.
+ */
+extern LIBTYPE struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool_t allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg);
+extern LIBTYPE void hdhomerun_video_destroy(struct hdhomerun_video_sock_t *vs);
+
+/*
+ * Get the port the socket is listening on.
+ *
+ * Returns 16-bit port with native endianness, or 0 on error.
+ */
+extern LIBTYPE uint16_t hdhomerun_video_get_local_port(struct hdhomerun_video_sock_t *vs);
+
+/*
+ * Join/leave multicast group.
+ */
+extern LIBTYPE int hdhomerun_video_join_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip);
+extern LIBTYPE void hdhomerun_video_leave_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip);
+
+/*
+ * Read data from buffer.
+ *
+ * size_t max_size: The maximum amount of data to be returned.
+ * size_t *pactual_size: The caller-supplied pactual_size value will be updated to contain the amount
+ * of data available to the caller.
+ *
+ * Returns a pointer to the data, or NULL if no data is available.
+ * The data will remain valid until another call to hdhomerun_video_recv.
+ *
+ * The amount of data returned will always be a multiple of VIDEO_DATA_PACKET_SIZE (1316).
+ * Attempting to read a single TS frame (188 bytes) will not return data as it is less than
+ * the minimum size.
+ *
+ * The buffer is implemented as a ring buffer. It is possible for this function to return a small
+ * amount of data when more is available due to the wrap-around case.
+ */
+extern LIBTYPE uint8_t *hdhomerun_video_recv(struct hdhomerun_video_sock_t *vs, size_t max_size, size_t *pactual_size);
+
+/*
+ * Flush the buffer.
+ */
+extern LIBTYPE void hdhomerun_video_flush(struct hdhomerun_video_sock_t *vs);
+
+/*
+ * Debug print internal stats.
+ */
+extern LIBTYPE void hdhomerun_video_debug_print_stats(struct hdhomerun_video_sock_t *vs);
+extern LIBTYPE void hdhomerun_video_get_stats(struct hdhomerun_video_sock_t *vs, struct hdhomerun_video_stats_t *stats);
+
+/*
+ * Internal use only.
+ */
+extern LIBTYPE hdhomerun_sock_t hdhomerun_video_get_sock(struct hdhomerun_video_sock_t *vs);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libhdhomerun/lgpl.txt b/libhdhomerun/lgpl.txt
new file mode 100755
index 0000000..fc8a5de
--- /dev/null
+++ b/libhdhomerun/lgpl.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.