Merge hdhomerun_drops into master
Update libhdhomerun and firmware to release 20130328
Conflicts:
libhdhomerun/hdhomerun_channels.c
Change-Id: Idac9378db9d8e32c2afad988460f90b07e9bc5d7
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1679151
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,49 @@
+# Copyright 2011 Google Inc. All Rights Reserved.
+# Author: qianzhang@google.com (ken Zhang)
+#MAKEFILE for cross platform
+
+DEBUG=-g
+ifdef DEBUG
+ DEBUG_CFLAGS= -g $(DEBUG)
+else
+ DEBUG_CFLAGS= -O3
+endif
+
+ifdef TARGET
+ CROSS_PREFIX:=$(TARGET)
+else
+ CROSS_PREFIX:=
+endif
+
+CC:=$(CROSS_PREFIX)gcc
+LD:=$(CROSS_PREFIX)ld
+AR:=$(CROSS_PREFIX)ar
+RANLIB:=$(CROSS_PREFIX)ranlib
+STRIP:=$(CROSS_PREFIX)strip
+
+CFLAGS= -Wall -fPIC -O2 -Wpointer-arith
+
+FILES=sagelog.c hdhomerun_tuner.c hdhomerun_http.c hdhomerun_dev.c utility.c hdhomerun_plugin.c
+INC= sagelog.h hdhomerun_tuner.h hdhomerun_http.h hdhomerun_dev.h utility.h
+EXTRA_INCS= ../tvstreamparser
+
+#all:hdhomerun
+all:hdhr_plugin.so
+dep_make:hdrun_dep
+
+hdhr_plugin.so:dep_make $(FILES) $(INC)
+ $(CC) $(CFLAGS) $(DEBUG_CFLAGS) $(FILES) -shared -o hdhr_plugin.so -I$(EXTRA_INCS) -L. -lhdhomerun -lpthread
+
+hdhomerun:dep_make $(FILES) $(INC)
+ $(CC) $(CFLAGS) $(DEBUG_CFLAGS) $(FILES) -DTEST_APP -o hdhomerun -I$(EXTRA_INCS) -L. -lhdhomerun -lpthread
+
+hdrun_dep:
+ make -C libhdhomerun
+ cp libhdhomerun/libhdhomerun.so .
+
+clean:
+ rm -f $(ALL) hdrun libhdhomerun.so hdhr_plugin.so get_http *~
+ make clean -C libhdhomerun
+
+get_http:hdhomerun_http.c hdhomerun_http.h
+ $(CC) $(CFLAGS) $(DEBUG_CFLAGS) -DHTTP_GET_APP hdhomerun_http.c -o get_http
diff --git a/channel_buffer.c b/channel_buffer.c
new file mode 100644
index 0000000..0a8ded0
--- /dev/null
+++ b/channel_buffer.c
@@ -0,0 +1,466 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (Qiann Zhang)
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/errno.h>
+#include <features.h>
+#include <sys/time.h>
+#include <math.h>
+#include <time.h>
+#include <sys/timeb.h>
+#include <sys/un.h>
+#include <poll.h>
+#include <pthread.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include "channel_buffer.h"
+
+static const char *delimiter = " \t,;\r\n:=|'\"#";
+
+static int is_delimiter(char ch)
+{
+ int i;
+ if (ch == 0)
+ return 1;
+ for (i = 0; delimiter[i]; i++)
+ if (ch == delimiter[i])
+ return 1;
+ return 0;
+}
+
+// format name:zzz (or name=zzzz)
+int get_string(char *p, const char *name, char *string, int max_len,
+ char **next)
+{
+ char *s, *e;
+ e = p;
+
+ while (e != NULL && *e) {
+ s = strstr(e, name);
+ if (s == NULL)
+ return 0;
+ // is a token name
+ if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+ is_delimiter(*(s + strlen(name)))) {
+ s += (int)strlen(name);
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s == '=' || *s == ':') {
+ s++;
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s) {
+ int i = 0;
+ while (i++ < max_len && *s && *s != ' ' && *s != '\t' && *s != '\n' &&
+ *s != '\r')
+ *string++ = *s++;
+
+ *string = 0x0; // terminator
+ if (next != NULL) {
+ while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r')
+ s++;
+ *next = s;
+ }
+ return 1;
+ }
+ }
+ }
+ e = ++s;
+ }
+ return 0;
+}
+
+// format name:"zzz" (or name="zzzz")
+int get_string_with_quote(char *p, const char *name, char *string, int max_len,
+ char **next)
+{
+ char *s, *e;
+ e = p;
+
+ while (e != NULL && *e) {
+ s = strstr(e, name);
+ if (s == NULL)
+ return 0;
+ // is a token name
+ if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+ is_delimiter(*(s + strlen(name)))) {
+ s += (int)strlen(name);
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s == '=' || *s == ':') {
+ s++;
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s == '\"') {
+ int i = 0;
+ s++;
+ while (i++ < max_len && *s && *s != '\"') *string++ = *s++;
+
+ *string = 0x0; // terminator
+
+ if (next != NULL) {
+ while (*s && *s != '\"') s++;
+ *next = s + 1;
+ }
+ return 1;
+ } else
+ return 0;
+ }
+ }
+ e = ++s;
+ }
+ return 0;
+}
+
+int get_string_token(char *p, int num, char *string, int max_len, char **next)
+{
+ int count = 0;
+ char *s;
+ s = p;
+ if (s == NULL)
+ return 0;
+ while (*s) {
+ while (*s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
+ s++; // skip white space
+ if (*s == 0)
+ break;
+
+ if (count++ != num) {
+ while (*s && !(*s == ' ' || *s == '\t' || *s == ';' || *s == ',' ||
+ *s == '\r' || *s == '\n'))
+ s++; // skip a token
+ } else {
+ if (*s) {
+ int i = 0;
+ while (i++ < max_len && *s && !(*s == ' ' || *s == '\t' || *s == ';' ||
+ *s == ',' || *s == '\r' || *s == '\n'))
+ *string++ = *s++;
+
+ *string = 0x0; // terminator
+ if (next != NULL) {
+ while (*s && !(*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
+ s++;
+ *next = s;
+ }
+ return 1;
+ }
+ }
+ if (*s)
+ s++;
+ }
+ return 0;
+}
+
+int get_int_val(char *p, const char *name, int *val, char **next)
+{
+ char *s, *e;
+ e = p;
+ while (e != NULL && *e) {
+ s = strstr(e, name);
+ if (s == NULL)
+ return 0;
+ // is a token name
+ if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+ is_delimiter(*(s + strlen(name)))) {
+ s += strlen(name);
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s == '=' || *s == ':') {
+ s++;
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s) {
+ *val = atoi(s);
+ if (*val < 0 && *s == '-')
+ s++;
+ while (*s >= '0' && *s <= '9') s++;
+ if (next != NULL)
+ *next = s;
+ return 1;
+ }
+ }
+ }
+ e = ++s;
+ }
+ return 0;
+}
+
+// format: name val; ext
+int get_cfg_val(char *string, char *name, int max_name_lenn, int *Val,
+ char *ext, int max_ext_len)
+{
+ const char *delima = " \t,;\r\n";
+ char *token, *saved_ptr;
+ if (name == NULL || ext == NULL || max_name_lenn == 0 || max_ext_len == 0)
+ return 0;
+
+ name[0] = 0;
+ *Val = 0;
+ ext[0] = 0;
+
+ token = (char *)strtok_r(string, delima, &saved_ptr);
+ if (token != NULL)
+ strncpy(name, token, max_name_lenn);
+ token = (char *)strtok_r(saved_ptr, delima, &saved_ptr);
+ if (token != NULL) {
+ *Val = atoi(token);
+ strncpy(ext, token, max_ext_len);
+ }
+ token = (char *)strtok_r(saved_ptr, delima, &saved_ptr);
+ if (token != NULL)
+ strncpy(ext, token, max_ext_len);
+ return 1;
+}
+
+int make_channel_entry_buffer(struct channel_entry_t *ce, char *buf, int size)
+{
+ int pos = 0;
+ pos +=
+ snprintf(buf + pos, size - pos, "ch:%03d state:%d ", ce->ch, ce->state);
+ if (ce->ctrl_bits)
+ pos += snprintf(buf + pos, size - pos, "ctrl:%d ", ce->ctrl_bits);
+ if (ce->ch_name[0])
+ pos += snprintf(buf + pos, size - pos, "name:%s ", ce->ch_name);
+ if (ce->src_ip[0])
+ pos += snprintf(buf + pos, size - pos, "ip:%s port:%d ", ce->src_ip,
+ ce->src_port);
+ if (ce->epg_id[0])
+ pos += snprintf(buf + pos, size - pos, "epg:%s ", ce->epg_id);
+ if (ce->dev_name[0])
+ pos += snprintf(buf + pos, size - pos, "dev:%s ", ce->dev_name);
+ if (ce->dev_tuning[0])
+ pos += snprintf(buf + pos, size - pos, "tune:%s ", ce->dev_tuning);
+ if (ce->av_inf[0])
+ pos += snprintf(buf + pos, size - pos, "av:\"%s\" ", ce->av_inf);
+ if (ce->ch_desc[0])
+ pos += snprintf(buf + pos, size - pos, "desc:%s ", ce->ch_desc);
+ pos += snprintf(buf + pos, size - pos, "\n");
+ return pos;
+}
+
+int parse_channel_entry_t_buffer(struct channel_entry_t *ce, char *buf)
+{
+ int item = 0;
+ char *p = buf;
+ if (!get_int_val(p, "ch", &ce->ch, NULL))
+ ce->ch = -1;
+ else if (!get_int_val(p, "state", &ce->state, NULL))
+ ce->ch = 1;
+ else
+ item++;
+ if (!get_int_val(p, "ctrl", &ce->ctrl_bits, NULL))
+ ce->ctrl_bits = 0;
+ else
+ item++;
+ if (!get_string(p, "name", ce->ch_name, sizeof(ce->ch_name) - 1, NULL))
+ ce->ch_name[0] = 0x0;
+ else
+ item++;
+ if (!get_string(p, "desc", ce->ch_desc, sizeof(ce->ch_desc) - 1, NULL))
+ ce->ch_desc[0] = 0x0;
+ else
+ item++;
+ if (!get_string(p, "ip", ce->src_ip, sizeof(ce->src_ip) - 1, NULL))
+ ce->src_ip[0] = 0x0;
+ else
+ item++;
+ if (!get_int_val(p, "port", &ce->src_port, NULL))
+ ce->src_port = -1;
+ else
+ item++;
+ if (!get_string(p, "epg", ce->epg_id, sizeof(ce->epg_id) - 1, NULL))
+ ce->epg_id[0] = 0x0;
+ else
+ item++;
+ if (!get_string(p, "dev", ce->dev_name, sizeof(ce->dev_name) - 1, NULL))
+ ce->dev_name[0] = 0x0;
+ else
+ item++;
+ if (!get_string(p, "tuning", ce->dev_tuning, sizeof(ce->dev_tuning) - 1,
+ NULL))
+ ce->dev_tuning[0] = 0x0;
+ else
+ item++;
+ if (!get_string_with_quote(p, "av", ce->av_inf, sizeof(ce->av_inf) - 1, NULL))
+ ce->av_inf[0] = 0x0;
+ else
+ item++;
+ if (!get_string_with_quote(p, "desc", ce->ch_desc, sizeof(ce->ch_desc) - 1,
+ NULL))
+ ce->ch_desc[0] = 0x0;
+ else
+ item++;
+
+ return item;
+}
+
+struct channel_list_t *create_channel_list(int num)
+{
+ if (num <= 0)
+ return NULL;
+ struct channel_list_t *cl = malloc(sizeof(struct channel_list_t));
+ if (cl == NULL)
+ return NULL;
+ cl->num = num;
+ cl->channel_num = 0;
+ cl->ce = malloc(sizeof(struct channel_entry_t) * num);
+ if (cl->ce == NULL) {
+ free(cl);
+ return NULL;
+ }
+ memset(cl->ce, 0x0, sizeof(struct channel_entry_t) * num);
+ return cl;
+}
+
+void release_channel_list(struct channel_list_t *cl)
+{
+ if (cl) {
+ if (cl->ce)
+ free(cl->ce);
+ free(cl);
+ }
+}
+
+struct channel_list_t *join_channel_list(struct channel_list_t *cl1,
+ struct channel_list_t *cl2)
+{
+ int i, n, channel_num;
+ if (cl1 == NULL)
+ return cl2;
+ if (cl2 == NULL)
+ return cl1;
+
+ channel_num = cl1->channel_num + cl2->channel_num;
+ if (channel_num < cl1->num) {
+ n = 0;
+ for (i = cl1->channel_num; i < channel_num; i++) {
+ cl1->ce[i] = cl2->ce[n++];
+ cl1->ce[i].ch = i;
+ }
+ cl2->channel_num = channel_num;
+ release_channel_list(cl2);
+ return cl1;
+ } else if (channel_num < cl2->num) {
+ n = 0;
+ for (i = cl2->channel_num; i < channel_num; i++) {
+ cl2->ce[i] = cl1->ce[n++];
+ cl2->ce[i].ch = i;
+ }
+ cl2->channel_num = channel_num;
+ release_channel_list(cl1);
+ return cl2;
+ } else {
+ struct channel_list_t *cl = create_channel_list(channel_num);
+ for (i = 0; i < cl1->channel_num; i++) {
+ cl->ce[i] = cl1->ce[i];
+ cl->ce[i].ch = i;
+ }
+ for (n = 0; n < cl2->channel_num; n++, i++) {
+ cl->ce[i] = cl2->ce[n];
+ cl->ce[i].ch = i;
+ }
+ release_channel_list(cl1);
+ release_channel_list(cl2);
+ cl->channel_num = channel_num;
+ return cl;
+ }
+ return NULL;
+}
+
+struct channel_list_t *load_channel_entry(char *file_name)
+{
+ int item;
+ int count = 0;
+ char buf[2048] = {0};
+ struct channel_list_t *cl;
+ FILE *fp;
+ fp = fopen(file_name, "r");
+ if (fp == NULL) {
+ // printf( "Can't open file %s to load channel table, errno:%d\n",
+ // file_name, errno );
+ return NULL;
+ }
+ // count entry number
+ while (!feof(fp)) {
+ char *p = fgets(buf, sizeof(buf) - 1, fp);
+ if (p == NULL)
+ break;
+ if (buf[0] != '#' && strlen(buf) > 4)
+ count++;
+ }
+ fseek(fp, 0, SEEK_SET);
+ cl = create_channel_list(count);
+ while (!feof(fp) && cl->channel_num < cl->num) {
+ char *p = fgets(buf, sizeof(buf) - 1, fp);
+ if (p == NULL)
+ break;
+
+ // skip comments
+ if (buf[0] == '#')
+ continue;
+
+ item = parse_channel_entry_t_buffer(&cl->ce[cl->channel_num], buf);
+ if (item > 0) {
+ cl->channel_num++;
+ }
+ }
+ fclose(fp);
+ return cl;
+}
+
+int save_channel_entry(char *file_name, struct channel_list_t *cl)
+{
+ int i;
+ char buf[2048];
+ FILE *fp;
+ if (cl == NULL || cl->channel_num == 0)
+ return 0;
+
+ fp = fopen(file_name, "w");
+ if (fp == NULL) {
+ // printf( "Can't open file %s to save channel table, errno:%d\n",
+ // file_name, errno );
+ return -1;
+ }
+ fputs("#Google sagetv channel table ver 1.0., maximum 2048 bytes per line\n",
+ fp);
+ fprintf(fp, "total_channel:%d\n", cl->channel_num);
+ for (i = 0; i < cl->channel_num; i++) {
+ make_channel_entry_buffer(&cl->ce[i], buf, sizeof(buf));
+ fputs(buf, fp);
+ }
+ fclose(fp);
+ return 1;
+}
+
+char *join_alloc_string(char *str1, char *str2)
+{
+ int len, len1, len2;
+ char *str;
+ if (str1 == NULL)
+ return str2;
+ if (str2 == NULL)
+ return str1;
+ len1 = strlen(str1);
+ len2 = strlen(str2);
+ len = len1 + len2;
+ str = (char *)malloc(len + 1);
+ strncpy(str, str1, len1);
+ strncpy(str + len1, str2, len2);
+ str[len] = 0x0;
+ free(str1);
+ free(str2);
+ return str;
+}
diff --git a/channel_buffer.h b/channel_buffer.h
new file mode 100644
index 0000000..194ebd1
--- /dev/null
+++ b/channel_buffer.h
@@ -0,0 +1,53 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (ken Zhang)
+
+#ifndef _CHANNEL_BUFFER_H_
+#define _CHANNEL_BUFFER_H_
+
+#ifndef MAX
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+#ifndef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+struct channel_entry_t {
+ int ch;
+ int state;
+ int ctrl_bits; // 0x01 fix multicast IP/port
+ char ch_name[20];
+ char ch_desc[32];
+ char src_ip[16];
+ int src_port;
+ char epg_id[8];
+ char dev_name[32];
+ char dev_tuning[16];
+ char av_inf[1024];
+ int frq;
+};
+
+struct channel_list_t {
+ int num;
+ int channel_num;
+ struct channel_entry_t *ce;
+};
+
+char *join_alloc_string(char *str1, char *str2);
+int get_string(char *p, const char *name, char *string, int max_len,
+ char **next);
+int get_string_with_quote(char *p, const char *name, char *string, int max_len,
+ char **next);
+int get_string_token(char *p, int num, char *string, int max_len, char **next);
+int get_int_val(char *p, const char *name, int *val, char **next);
+
+struct channel_list_t *create_channel_list(int num);
+void release_channel_list(struct channel_list_t *cl);
+struct channel_list_t *join_channel_list(struct channel_list_t *cl1,
+ struct channel_list_t *cl2);
+
+int make_channel_entry_buffer(struct channel_entry_t *ce, char *buf, int size);
+int parse_channel_entry_buffer(struct channel_entry_t *ce, char *buf);
+struct channel_list_t *load_channel_entry(char *file_name);
+int save_channel_entry(char *file_name, struct channel_list_t *cl);
+
+#endif
diff --git a/hdhomerun_dev.c b/hdhomerun_dev.c
new file mode 100644
index 0000000..62e12fa
--- /dev/null
+++ b/hdhomerun_dev.c
@@ -0,0 +1,123 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include "utility.h"
+#include "hdhomerun_tuner.h"
+#include "hdhomerun_http.h"
+#include "hdhomerun_dev.h"
+#include "sagelog.h"
+
+#define _MIN_(x, y) ((x) > (y) ? (y) : (x))
+
+static int read_elemnt(char *xml, char *tag, char *buf, int size)
+{
+ char open_tag[256], close_tag[256];
+ char *start, *end;
+ snprintf(open_tag, sizeof(open_tag), "<%s>", tag);
+ snprintf(close_tag, sizeof(open_tag), "</%s>", tag);
+
+ start = strstr(xml, open_tag);
+ if (start != NULL) {
+ start += strlen(open_tag);
+ end = strstr(start, close_tag);
+ if (end != NULL) {
+ int len = _MIN_(end - start, size);
+ memcpy(buf, start, len);
+ buf[len] = 0;
+ end += strlen(close_tag);
+ return end - xml;
+ }
+ }
+ return 0;
+}
+
+static void html_txt_converison(char *txt)
+{
+ char *p;
+ do {
+ // remove &
+ p = strstr(txt, "&");
+ if (p)
+ strcpy(p + 1, p + 1 + 4);
+ } while (p);
+}
+
+// remove tag and trim space
+static void cleanup_txt(char *txt)
+{
+ int n = strlen(txt);
+ while (n > 0) {
+ if (txt[n] == ' ' || txt[n] == '<' || txt[n] == '\r' || txt[n] == '\n')
+ txt[n] = 0x0;
+ n--;
+ }
+}
+
+static int program_parser(void *context, char *buf, int bytes)
+{
+ struct vchan_tbl_t *vt = (struct vchan_tbl_t *)context;
+ int pos, used_bytes;
+ char guide_num[16], guide_name[64];
+
+ pos = 0;
+ while (pos < bytes) {
+ used_bytes =
+ read_elemnt(buf + pos, "GuideNumber", guide_num, sizeof(guide_num));
+ if (used_bytes == 0)
+ break;
+ pos += used_bytes;
+ // printf( "%s ", guide_num );
+ used_bytes =
+ read_elemnt(buf + pos, "GuideName", guide_name, sizeof(guide_name));
+ if (used_bytes == 0)
+ break;
+ html_txt_converison(guide_name);
+ if (vt->channel_num >= vt->channel_size)
+ grow_vhcan_tbl(vt);
+ if (vt->channel_num < vt->channel_size) {
+ int i = vt->channel_num;
+ vt->vchan[i].guide_id = atoi(guide_num);
+ snprintf(vt->vchan[i].guide_name, sizeof(vt->vchan[i].guide_name),
+ "\"%s\"", guide_name);
+ vt->channel_num++;
+ }
+ pos += used_bytes;
+ // printf( "%s;\n", guide_name );
+ }
+ return pos;
+}
+
+static int hdhomerun_parser(void *context, char *buf, int bytes)
+{
+ char *p;
+ struct hdhr_tuner_t *ht = (struct hdhr_tuner_t *)context;
+ p = strstr(buf, "Model:");
+ if (p != NULL) {
+ if (strlen(p + 6) < 10)
+ return p - buf;
+ if (get_string_by_name(p, "Model", ht->model, sizeof(ht->model), NULL)) {
+ cleanup_txt(ht->model);
+ }
+ }
+
+ if (strstr(buf, "CableCARD Menu") != NULL) {
+ ht->cc_tuner = 1;
+ }
+ return bytes;
+}
+
+int get_dev_model(struct hdhr_tuner_t *ht)
+{
+ int ret = parse_http_page(ht->ip, "/", hdhomerun_parser, ht);
+ return ret;
+}
+
+int get_vchannel_list(struct vchan_tbl_t *vt)
+{
+ int ret = parse_http_page(vt->ht.ip, "/lineup.xml", program_parser, vt);
+ return ret;
+}
diff --git a/hdhomerun_dev.h b/hdhomerun_dev.h
new file mode 100644
index 0000000..2848ce0
--- /dev/null
+++ b/hdhomerun_dev.h
@@ -0,0 +1,7 @@
+#ifndef _HDHOMERUN_DEV_H_
+#define _HDHOMERUN_DEV_H_
+
+int get_dev_model(struct hdhr_tuner_t *ht);
+int get_vchannel_list(struct vchan_tbl_t *vt);
+
+#endif
diff --git a/hdhomerun_http.c b/hdhomerun_http.c
new file mode 100644
index 0000000..a22c57e
--- /dev/null
+++ b/hdhomerun_http.c
@@ -0,0 +1,147 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <string.h>
+#include "hdhomerun_http.h"
+
+#define HOST "google.com"
+#define PAGE "/"
+#define PORT 80
+#define USERAGENT "SAGETV 1.0"
+
+#define _MIN_(x, y) ((x) > (y) ? (y) : (x))
+
+static char *get_ip(char *host, char *buf, int size)
+{
+ struct hostent *hent;
+ char *ip = buf;
+ memset(ip, 0, size);
+ if ((hent = gethostbyname(host)) == NULL) {
+ herror("Can't get IP");
+ exit(1);
+ }
+ if (inet_ntop(AF_INET, (void *)hent->h_addr_list[0], ip, size) == NULL) {
+ perror("Can't resolve host");
+ exit(1);
+ }
+ return ip;
+}
+
+static char *build_get_query(char *host, char *page)
+{
+ char *query;
+ char *getpage = page;
+ char *tpl = "GET /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n";
+ if (getpage[0] == '/') {
+ getpage = getpage + 1;
+ }
+ // -5 is to consider the %s %s %s in tpl and the ending \0
+ query = (char *)malloc(strlen(host) + strlen(getpage) + strlen(USERAGENT) +
+ strlen(tpl) - 5);
+ sprintf(query, tpl, getpage, host, USERAGENT);
+ return query;
+}
+
+int parse_http_page(char *host, char *page, parser_f parser, void *context)
+{
+ struct sockaddr_in *remote;
+ int ret;
+ char buf[BUFSIZ + 1];
+ int sock;
+ char *get;
+ char ip[32];
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+ perror("Can't create TCP socket");
+ return -1;
+ }
+
+ get_ip(host, ip, sizeof(ip));
+ remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *));
+ remote->sin_family = AF_INET;
+ ret = inet_pton(AF_INET, ip, (void *)(&(remote->sin_addr.s_addr)));
+ if (ret < 0) {
+ perror("Can't set remote->sin_addr.s_addr");
+ return -1;
+ } else if (ret == 0) {
+ fprintf(stderr, "%s is not a valid IP address\n", ip);
+ return -1;
+ }
+ remote->sin_port = htons(PORT);
+
+ if (connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0) {
+ perror("Could not connect");
+ return -1;
+ }
+ get = build_get_query(host, page);
+
+ // send the query to the server
+ int sent = 0;
+ while (sent < strlen(get)) {
+ ret = send(sock, get + sent, strlen(get) - sent, 0);
+ if (ret == -1) {
+ perror("Can't send query");
+ break;
+ }
+ sent += ret;
+ }
+
+ // receive page
+ int remain = 0;
+ while ((ret = recv(sock, buf + remain, BUFSIZ - remain, 0)) > 0) {
+ int used_bytes = 0;
+ if (parser != NULL && ret >= 0) {
+ *(buf + remain + ret) = 0x0;
+ used_bytes = parser(context, buf, ret + remain);
+ remain = ret + remain - used_bytes;
+ if (remain > 0)
+ memcpy(buf, buf + used_bytes, remain);
+ }
+ if (used_bytes < 0)
+ break;
+ }
+ free(get);
+ free(remote);
+ close(sock);
+ return 1;
+}
+
+#ifdef HTTP_GET_APP
+static void usage(void)
+{
+ fprintf(stderr,
+ "USAGE: htmlget host [page]\n"
+ "\thost: the website hostname. ex: coding.debuntu.org\n"
+ "\tpage: the page to retrieve. ex: index.html, default: /\n");
+}
+
+static int noop_parser(void *context, char *buf, int bytes)
+{
+ printf("%s", buf);
+ return bytes;
+}
+
+int main(int argc, char **argv)
+{
+ char *host;
+ char *page;
+
+ if (argc == 1) {
+ usage();
+ exit(2);
+ }
+ host = argv[1];
+ if (argc > 2) {
+ page = argv[2];
+ } else {
+ page = PAGE;
+ }
+
+ parse_http_page(host, page, noop_parser, NULL);
+ return 0;
+}
+
+#endif
diff --git a/hdhomerun_http.h b/hdhomerun_http.h
new file mode 100644
index 0000000..0c61c7b
--- /dev/null
+++ b/hdhomerun_http.h
@@ -0,0 +1,7 @@
+#ifndef _HDHOMERUN_HTTP_H_
+#define _HDHOMERUN_HTTP_H_
+
+typedef int (*parser_f)(void *, char *, int);
+int parse_http_page(char *host, char *page, parser_f parser, void *context);
+
+#endif
diff --git a/hdhomerun_plugin.c b/hdhomerun_plugin.c
new file mode 100644
index 0000000..adc2c25
--- /dev/null
+++ b/hdhomerun_plugin.c
@@ -0,0 +1,337 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (Qian Zhang)
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <assert.h>
+#include "utility.h"
+#include "hdhomerun_plugin.h"
+#include "libhdhomerun/hdhomerun.h"
+#include "hdhomerun_tuner.h"
+#include "hdhomerun_dev.h"
+#include "hdhomerun_tuner.h"
+#include "channel_buffer.h"
+#include "sagelog.h"
+
+#define MAX_TUNER_NUM 16
+
+struct device_list {
+ int tuner_num;
+ int list_num;
+ struct hdhr_tuner_t *hdhrs;
+};
+
+static int discover_hdhrs(struct hdhr_tuner_t *ht, int max_ht_num)
+{
+ int i, ret = 0;
+ ret = discover_device(ht, max_ht_num);
+ if (ret > 0) {
+ for (i = 0; i < ret; i++) {
+ // if a unit shares a source input in a device, copy data.
+ if (i > 0 && tuner_input_sharing(&ht[i - 1], &ht[i])) {
+ ht[i].cc_tuner = ht[i - 1].cc_tuner;
+ strncpy(ht[i].model, ht[i - 1].model, sizeof(ht[i].model));
+ } else
+ get_dev_model(&ht[i]);
+ }
+ return i;
+ }
+ return 0;
+}
+
+void *dev_init(int *num)
+{
+ int i, index = 0, tuner_num;
+ struct device_list *devices;
+
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun device init"));
+
+ devices = (struct device_list *)malloc(sizeof(struct device_list));
+ if (devices != NULL) {
+ devices->hdhrs = (struct hdhr_tuner_t *)malloc(sizeof(struct hdhr_tuner_t) *
+ MAX_TUNER_NUM);
+ memset(devices->hdhrs, 0,
+ sizeof(sizeof(struct hdhr_tuner_t) * MAX_TUNER_NUM));
+ if (devices->hdhrs == NULL) {
+ free(devices);
+ return 0;
+ }
+ devices->list_num = MAX_TUNER_NUM;
+ devices->tuner_num = 0;
+ tuner_num = discover_hdhrs(devices->hdhrs, MAX_TUNER_NUM);
+ for (i = 0; i < tuner_num; i++) {
+ devices->hdhrs[i].tuner_id = index++;
+ snprintf(devices->hdhrs[i].tuner_name,
+ sizeof(devices->hdhrs[i].tuner_name), "%s-%s",
+ devices->hdhrs[i].model, devices->hdhrs[i].name);
+ devices->tuner_num++;
+ sage_log((_LOG_TRACE, 3, "hdhr:%d %s %s %s", i,
+ devices->hdhrs[i].tuner_name, devices->hdhrs[i].model,
+ devices->hdhrs[i].cc_tuner ? "CableCard" : ""));
+ }
+ devices->tuner_num = tuner_num;
+ *num = tuner_num;
+ return devices;
+ }
+ return NULL;
+}
+
+void dev_release(void *handle)
+{
+ struct device_list *devices = (struct device_list *)handle;
+ if (devices != NULL) {
+ if (devices->hdhrs != NULL)
+ free(devices->hdhrs);
+ free(devices);
+ }
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun device release."));
+}
+
+int get_dev_name(void *handle, int index, char *name, int max_name_len)
+{
+ struct device_list *devices = (struct device_list *)handle;
+ if (devices != NULL && index < devices->tuner_num) {
+ struct hdhr_tuner_t *hdhr = &devices->hdhrs[index];
+ strncpy(name, hdhr->tuner_name, max_name_len);
+ return 1;
+ }
+ return 0;
+}
+
+void *dev_open(void *handle, int index, int attr)
+{
+ struct device_list *devices = (struct device_list *)handle;
+ struct hdhr_tuner_t *hdhr;
+ if (index >= devices->tuner_num)
+ return NULL;
+ hdhr = &devices->hdhrs[index];
+ hdhr->hd = open_hdhr(hdhr->name);
+ return hdhr;
+}
+
+void dev_close(void *dev)
+{
+ struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+ if (hdhr == NULL) {
+ sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+ return;
+ }
+ stop_channel(hdhr->hd);
+ close_hdhr(hdhr->hd);
+}
+
+int dev_tune(void *dev, char *channel, char *stream_tar)
+{
+ int ret;
+ struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+ if (hdhr == NULL) {
+ sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+ return 0;
+ }
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun tune %s %s.", hdhr->name,
+ (char *)channel));
+ if (strstr(stream_tar, "udp://") == NULL) {
+ sage_log((_LOG_TRACE, 3, "hdhr:ERROR: stream target \"%s\" is wrong.",
+ stream_tar));
+ return 0;
+ }
+
+ if (hdhr->cc_tuner) {
+ // HDHomeRun PRIME
+ if (strstr(channel, "vchan:") == NULL) {
+ char vchan[32];
+ snprintf(vchan, sizeof(vchan), "vchan:%d", atoi(channel));
+ ret = tune_channel(hdhr->hd, vchan, "", stream_tar);
+ } else
+ ret = tune_channel(hdhr->hd, channel, "", stream_tar);
+ } else {
+ // HDHomeRun US
+ char tune_str[32], map_str[32], prog_str[8];
+ int ch = -1, prog = -1, vch_major = -1, vch_minor = -1;
+ char *ps, *pe;
+ map_str[0] = 0x0;
+
+ // channel format: xx-yy-zz
+ // xx=physical channel, yy=virtual major channel, zz=virtual minor channel
+ if (sscanf(channel, "%d-%d-%d", &ch, &vch_major, &vch_minor) == 3) {
+ ret = tune_vchannel(hdhr->hd, ch, vch_major, vch_minor, stream_tar);
+ goto out;
+ }
+
+ // channel format: xx-yy
+ // xx=physical channel, yy=virtual major channel
+ if (sscanf(channel, "%d-%d", &ch, &vch_major) == 2) {
+ vch_minor = 0;
+ ret = tune_vchannel(hdhr->hd, ch, vch_major, vch_minor, stream_tar);
+ goto out;
+ }
+
+ // if channel in format "map:xxx-yy|auto:zzz prog:ppp"
+ if ((ps = strstr(channel, "map:")) != NULL &&
+ (pe = strchr(ps + 4, '|')) != NULL) {
+ strncpy(map_str, ps + 4, pe - (ps + 4));
+ map_str[pe - (ps + 4)] = 0x0;
+ }
+ get_int_val_by_name(channel, "auto", &ch, NULL);
+ get_int_val_by_name(channel, "prog", &prog, NULL);
+ if (prog == -1) {
+ sage_log(
+ (_LOG_TRACE, 3, "hdhr:ERROR: invalid channel string %s", channel));
+ return 0;
+ }
+ // if channel in format "nnn prog:mmm", based on dev to guess map
+ if (map_str[0] == 0x0 && prog > 0 && ch == -1) {
+ ch = atoi(channel);
+ if (ch > 0) {
+ snprintf(map_str, sizeof(map_str), "map:us-bcast");
+ }
+ }
+ if (map_str[0] && ch > 0 && prog > 0) {
+ snprintf(tune_str, sizeof(tune_str), "%s|auto:%d", map_str, ch);
+ snprintf(prog_str, sizeof(prog), "%d", prog);
+ ret = tune_channel(hdhr->hd, tune_str, prog_str, stream_tar);
+ } else {
+ sage_log(
+ (_LOG_ERROR, 3, "hdhr:ERROR: invalid channel string %s", channel));
+ return 0;
+ }
+ }
+
+out:
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun tune %s %s.", (char *)channel,
+ ret > 0 ? "successful" : "failed"));
+ return ret > 0;
+}
+
+int dev_stop(void *dev)
+{
+ struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+ if (hdhr == NULL) {
+ sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+ return 0;
+ }
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun stop %s.", (char *)hdhr->name));
+ int ret = stop_channel(hdhr->hd);
+ return ret > 0;
+}
+
+int dev_scan(void *dev, char *tuner_name, char *channel,
+ struct channel_list_t **cl_p)
+{
+ struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+ if (hdhr == NULL) {
+ sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+ return 0;
+ }
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun scan %s.", (char *)hdhr->name));
+ if (hdhr->cc_tuner) {
+ int vchannel;
+ char *p;
+ if ((p = strstr(channel, "vchan:")) == NULL)
+ vchannel = atoi(channel);
+ else
+ vchannel = atoi(p + 6);
+ if (vchannel == 0)
+ return 0;
+
+ struct channel_list_t *cl = _create_channel_list(1);
+ int ret = scan_vchannel(hdhr->hd, vchannel, cl->ce);
+ char unit_name[16];
+ snprintf(cl->ce[0].dev_name, sizeof(cl->ce[0].dev_name) - 1, "HDHR.%s",
+ get_unit_name(hdhr, unit_name, sizeof(unit_name)));
+ cl->channel_num = ret;
+ if (ret == 0) {
+ _release_channel_list(cl);
+ cl = NULL;
+ }
+ if (cl_p != NULL)
+ *cl_p = cl;
+ return ret;
+ } else {
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun ATSC dev_scan not implemented %s.",
+ (char *)hdhr->name));
+ }
+
+ return 0;
+}
+
+static struct channel_list_t *build_hdhr_channel_table(
+ struct hdhr_tuner_t *hdhr)
+{
+ if (hdhr->cc_tuner) {
+ struct vchan_tbl_t *vt = create_vchan_tbl(hdhr);
+ struct channel_list_t *cl = NULL;
+ if (get_vchannel_list(vt) > 0) {
+ int num = vt->channel_num;
+ cl = _create_channel_list(num);
+ int i;
+ for (i = 0; i < num; i++) {
+ char unit_name[16];
+ cl->ce[i].state = 1;
+ snprintf(cl->ce[i].dev_tuning, sizeof(cl->ce[i].dev_tuning) - 1,
+ "vchan:%d", vt->vchan[i].guide_id);
+ snprintf(cl->ce[i].ch_name, sizeof(cl->ce[i].ch_name) - 1, "%d",
+ vt->vchan[i].guide_id);
+ strncpy(cl->ce[i].ch_desc, vt->vchan[i].guide_name,
+ sizeof(cl->ce[i].ch_desc) - 1);
+ cl->ce[i].src_ip[0] = 0x0;
+ cl->ce[i].src_port = 0; // dynamic assigning
+ snprintf(cl->ce[i].dev_name, sizeof(cl->ce[i].dev_name) - 1, "HDHR.%s",
+ get_unit_name(hdhr, unit_name, sizeof(unit_name)));
+ cl->ce[i].ch = vt->vchan[i].guide_id;
+ cl->ce[i].frq = 0;
+ cl->ce[i].av_inf[0] = 0;
+ }
+ cl->channel_num = i;
+ }
+ release_vchan_tbl(vt);
+ return cl;
+ } else {
+ sage_log(
+ (_LOG_TRACE, 3,
+ "hdhr:hdhomerun ATSC build_hdhr_channel_table not implemented %s.",
+ (char *)hdhr->name));
+ }
+ return NULL;
+}
+
+int dev_build_channel_table(void *dev, char *option,
+ struct channel_list_t **cl_p)
+{
+ struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+ if (hdhr == NULL) {
+ sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+ return 0;
+ }
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun dev_build_channel_table %s.",
+ (char *)hdhr->name));
+ struct channel_list_t *cl = build_hdhr_channel_table(hdhr);
+ if (cl_p != NULL) {
+ *cl_p = cl;
+ if (cl == NULL)
+ return 0;
+ return cl->channel_num;
+ }
+ return 0;
+}
+
+int dev_release_channel_table(void *dev)
+{
+ struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+ if (hdhr == NULL) {
+ sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+ return 0;
+ }
+ sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun dev_release_channel_table %s.",
+ (char *)hdhr->name));
+ if (hdhr->channel_table != NULL) {
+ free(hdhr->channel_table);
+ hdhr->channel_table = NULL;
+ return 1;
+ } else { // TODO
+ }
+ return 0;
+}
diff --git a/hdhomerun_plugin.h b/hdhomerun_plugin.h
new file mode 100644
index 0000000..8d8fc84
--- /dev/null
+++ b/hdhomerun_plugin.h
@@ -0,0 +1,17 @@
+#ifndef _HDHOMERUN_PLUGIN_H_
+#define _HDHOMERUN_PLUGIN_H_
+
+void *dev_init(int *num); // return tuner handle
+void dev_release(void *handle); // release tuner
+int get_dev_name(void *handle, int index, char *name, int max_name_len);
+void *dev_open(void *handle, int index, int attr);
+void dev_close(void *dev);
+int dev_tune(void *dev, char *channel, char *stream_tar);
+int dev_stop(void *dev);
+int dev_scan(void *dev, char *tuner_name, char *channel,
+ struct channel_list_t **cl_p);
+int dev_build_channel_table(void *dev, char *option,
+ struct channel_list_t **cl_p);
+int dev_release_channel_table(void *dev);
+
+#endif
diff --git a/hdhomerun_tuner.c b/hdhomerun_tuner.c
new file mode 100644
index 0000000..bff2315
--- /dev/null
+++ b/hdhomerun_tuner.c
@@ -0,0 +1,802 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (Qian Zhang)
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include "utility.h"
+#include "channel_buffer.h"
+#include "libhdhomerun/hdhomerun.h"
+#include "hdhomerun_tuner.h"
+#include "hdhomerun_dev.h"
+#include "sagelog.h"
+
+#define PARSE_TIMEOUT 2
+#define CHANNE_SCAN_SRC "227.0.0.1"
+#define CHANNEL_SCAN_PORT 6069
+
+static volatile sig_atomic_t sigabort_flag = FALSE;
+static volatile sig_atomic_t siginfo_flag = FALSE;
+
+static const char *map_tbl[] = {"us-bcast", "us-cable", "us-hrc", "us-irc",
+ "au-bcast", "tw-bcast", "eu-bcast", 0x0};
+
+static void sigabort_handler(int arg)
+{
+ sigabort_flag = TRUE;
+}
+
+static void siginfo_handler(int arg)
+{
+ siginfo_flag = TRUE;
+}
+
+static void register_signal_handlers(sig_t sigpipe_handler,
+ sig_t sigint_handler,
+ sig_t siginfo_handler)
+{
+#if defined(SIGPIPE)
+ signal(SIGPIPE, sigpipe_handler);
+#endif
+#if defined(SIGINT)
+ signal(SIGINT, sigint_handler);
+#endif
+#if defined(SIGINFO)
+ signal(SIGINFO, siginfo_handler);
+#endif
+}
+
+// channel format: map:us-cable,auto:68
+void *open_hdhr(char *name)
+{
+ struct hdhomerun_device_t *hd;
+ hd = hdhomerun_device_create_from_str(name, NULL);
+ return hd;
+}
+
+void close_hdhr(void *device)
+{
+ struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+ if (hd) {
+ hdhomerun_device_stream_stop(hd);
+ hdhomerun_device_destroy(hd);
+ }
+}
+
+int tune_channel(void *device, char *channel, char *program,
+ char *stream_target)
+{
+ char channel_str[32] = "auto:";
+ char vchannel_str[16];
+ char channel_map[32];
+ struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+ int ret;
+
+ if (hd == NULL) {
+ printf("hdhomerun invalid handle\n");
+ return -1;
+ }
+
+ if (!get_string_by_name(channel, "vchan", vchannel_str, sizeof(vchannel_str),
+ NULL)) {
+ vchannel_str[0] = 0x0;
+
+ if (!get_string_by_name(channel, "auto", channel_str + 5,
+ sizeof(channel_str) - 5, NULL)) {
+ printf("hdhomerun %p invalid channel %s\n", device, channel);
+ return -1;
+ }
+ if (!get_string_by_name(channel, "map", channel_map, sizeof(channel_map),
+ NULL)) {
+ printf("hdhomerun %p invalid channel map %s\n", device, channel);
+ strncpy(channel_map, "us-cable", sizeof(channel_map));
+ }
+ if (program[0] == 0x0) {
+ printf("hdhomerun %p invalid channel program %s\n", device, program);
+ return -1;
+ }
+ ret = hdhomerun_device_set_tuner_channelmap(hd, channel_map);
+ if (ret < 0)
+ printf("hdhomerun failed to set tuner channel map %s\n", channel_map);
+
+ ret = hdhomerun_device_set_tuner_channel(hd, channel_str);
+ if (ret < 0)
+ printf("hdhomerun tuning failed:%s ret:%d\n", channel_str, ret);
+
+ ret = hdhomerun_device_set_tuner_program(hd, program);
+ if (ret <= 0)
+ printf("hdhomerun failed set program:%s ret:%d\n", program, ret);
+ } else {
+ ret = hdhomerun_device_set_tuner_vchannel(hd, vchannel_str);
+ if (ret < 0)
+ printf("hdhomerun tuning failed: vchannel:%s ret:%d\n", vchannel_str,
+ ret);
+ }
+
+ char target[64];
+ snprintf(target, sizeof(target), "%s ttl=2", stream_target);
+ ret = hdhomerun_device_set_tuner_target(hd, target);
+ if (ret < 0) {
+ printf("hdhomerun failed set target %s ret:%d\n", target, ret);
+ }
+
+ return ret;
+}
+
+int vchan_to_prog_num(char *streaminfo, unsigned int vchan_major,
+ unsigned int vchan_minor, unsigned int *prog_num)
+{
+ char *line;
+ char *next_line;
+ unsigned int program_number;
+ unsigned int virtual_major, virtual_minor;
+
+ next_line = streaminfo;
+
+ while (1) {
+ line = next_line;
+ next_line = strchr(line, '\n');
+ if (!next_line) {
+ break;
+ }
+ *next_line++ = 0;
+
+ 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;
+ }
+
+ if (vchan_major == virtual_major && vchan_minor == virtual_minor) {
+ *prog_num = program_number;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int tune_vchannel(void *device, unsigned int channel,
+ unsigned int vchannel_major, unsigned int vchannel_minor,
+ char *stream_target)
+{
+ char channel_map[16];
+ char channel_str[16];
+ char program_str[16];
+ char target_str[64];
+ struct hdhomerun_device_t *hd;
+ struct hdhomerun_tuner_status_t ts;
+ char *streaminfo;
+ unsigned int program;
+ int i, ret;
+
+ hd = (struct hdhomerun_device_t *)device;
+
+ // Set tuner channel map to US broadcast
+ snprintf(channel_map, sizeof(channel_map), "us-bcast");
+ ret = hdhomerun_device_set_tuner_channelmap(hd, channel_map);
+ if (ret < 0) {
+ printf("hdhr:ERROR: set tuner channel map %s\n", channel_map);
+ goto out;
+ }
+
+ // Tune to physical channel
+ snprintf(channel_str, sizeof(channel_str), "auto:%u", channel);
+ ret = hdhomerun_device_set_tuner_channel(hd, channel_str);
+ if (ret <= 0) {
+ printf("hdhr:ERROR: set tuner channel %s\n", channel_str);
+ goto out;
+ }
+
+ // Wait for SER to hit 100% (max 3 secs)
+ usleep(500000);
+ for (i = 0; i < 10; i++) {
+ ret = hdhomerun_device_get_tuner_status(hd, NULL, &ts);
+ if (ret > 0 && ts.symbol_error_quality >= 100) {
+ break;
+ }
+ usleep(250000);
+ }
+
+ // Get MPEG2-TS stream info
+ ret = hdhomerun_device_get_tuner_streaminfo(hd, &streaminfo);
+ if (ret <= 0) {
+ printf("hdhr:ERROR: get tuner streaminfo\n");
+ goto out;
+ }
+ printf("hdhr:hdhomerun streaminfo:\n%s", streaminfo);
+
+ // Translate virtual channel (major.minor) to program number
+ ret = vchan_to_prog_num(streaminfo, vchannel_major, vchannel_minor, &program);
+ if (ret <= 0) {
+ printf("hdhr:ERROR: vchannel %d.%d not found in streaminfo\n",
+ vchannel_major, vchannel_minor);
+ goto out;
+ }
+ printf("hdhr:hdhomerun vchannel %u.%u -> program %u\n", vchannel_major,
+ vchannel_minor, program);
+
+ // Set tuner program
+ snprintf(program_str, sizeof(program_str), "%u", program);
+ ret = hdhomerun_device_set_tuner_program(hd, program_str);
+ if (ret <= 0) {
+ printf("hdhr:ERROR: set tuner program %s\n", program_str);
+ goto out;
+ }
+
+ // Set tuner stream target
+ snprintf(target_str, sizeof(target_str), "%s ttl=2", stream_target);
+ ret = hdhomerun_device_set_tuner_target(hd, target_str);
+ if (ret <= 0) {
+ printf("hdhr:ERROR: set tuner target %s\n", target_str);
+ }
+
+out:
+ return ret;
+}
+
+// channel format: map:us-cable,auto:68
+int stop_channel(void *device)
+{
+ int ret;
+ struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+ if (hd == NULL) {
+ printf("hdhomerun invalid handle\n");
+ return -1;
+ }
+
+ ret = hdhomerun_device_set_tuner_channel(hd, "none");
+ if (ret < 0) {
+ printf("hdhomerun failed set channel none to stop streaming ret:%d\n",
+ ret);
+ }
+
+ ret = hdhomerun_device_set_tuner_vchannel(hd, "none");
+ if (ret < 0) {
+ printf("hdhomerun failed set vchannel none to stop streaming ret:%d\n",
+ ret);
+ }
+
+ ret = hdhomerun_device_set_tuner_target(hd, "none");
+ if (ret < 0) {
+ printf("hdhomerun failed set target none to stop streaming ret:%d\n", ret);
+ }
+
+ return ret;
+}
+
+char *get_unit_name(struct hdhr_tuner_t *ht, char *name, int size)
+{
+ char *p = strchr(ht->name, '-');
+ if (p != NULL) {
+ int len = p - ht->name;
+ strncpy(name, ht->name, len);
+ name[len] = 0x0;
+ return name;
+ }
+ name[0] = 0x0;
+ return name;
+}
+
+static char *ip4_address(int ip4, char *buf, int size)
+{
+ snprintf(buf, size, "%u.%u.%u.%u", (unsigned int)(ip4 >> 24) & 0xFF,
+ (unsigned int)(ip4 >> 16) & 0xFF, (unsigned int)(ip4 >> 8) & 0xFF,
+ (unsigned int)(ip4 >> 0) & 0xFF);
+ return buf;
+}
+
+int discover_device(struct hdhr_tuner_t *ht, int max_ht_num)
+{
+ int i, k, num = 0;
+ struct hdhomerun_discover_device_t *device_info =
+ malloc(max_ht_num * sizeof(struct hdhomerun_discover_device_t));
+ int ret = hdhomerun_discover_find_devices_custom(
+ 0, HDHOMERUN_DEVICE_TYPE_TUNER, -1, device_info, 16);
+ if (ret <= 0) {
+ free(device_info);
+ return 0;
+ }
+ // printf( "found %d hdhomerun device\n", ret );
+ for (i = 0; i < ret; i++) {
+ // printf( "ip:%x type:%x id:%x num:%d\n", device_info[i].ip_addr,
+ // device_info[i].device_type, device_info[i].device_id,
+ // device_info[i].tuner_count );
+ for (k = 0; k < device_info[i].tuner_count && num < max_ht_num; k++) {
+ snprintf(ht[num].name, sizeof(ht[num].name), "%X-%d",
+ device_info[i].device_id, k);
+ ip4_address(device_info[i].ip_addr, ht[num].ip, sizeof(ht[num].ip));
+ ht[num].type = device_info[i].device_type;
+ ht[num].cc_tuner = 0;
+ ht[num].model[0] = 0;
+ num++;
+ }
+ }
+ free(device_info);
+ return num;
+}
+
+static char *get_tune_string(char *result, char *tune_str, int tune_str_len)
+{
+ int i = 0;
+ int ch;
+ while (map_tbl[i][0]) {
+ if (get_int_val_by_name(result, map_tbl[i], &ch, NULL) > 0) {
+ snprintf(tune_str, tune_str_len, "map:%s|auto:%d", map_tbl[i], ch);
+ return tune_str;
+ }
+ i++;
+ }
+ return "";
+}
+
+static int get_tune_channel(char *result)
+{
+ int i = 0;
+ int ch;
+ while (map_tbl[i][0]) {
+ if (get_int_val_by_name(result, map_tbl[i], &ch, NULL) > 0) {
+ return ch;
+ }
+ i++;
+ }
+ return 0;
+}
+
+int scan_all(void *device, struct channel_entry_t *ce, int ce_num)
+{
+ int found_channel = 0;
+ struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+ if (hd == NULL) {
+ printf("hdhomerun invalid handle\n");
+ return -1;
+ }
+
+ char *ret_error;
+ if (hdhomerun_device_tuner_lockkey_request(hd, &ret_error) <= 0) {
+ fprintf(stderr, "failed to lock tuner\n");
+ if (ret_error) {
+ fprintf(stderr, "%s\n", ret_error);
+ }
+ return -1;
+ }
+
+ hdhomerun_device_set_tuner_target(hd, "none");
+
+ char *channelmap;
+ if (hdhomerun_device_get_tuner_channelmap(hd, &channelmap) <= 0) {
+ fprintf(stderr, "failed to query channelmap from device\n");
+ return -1;
+ }
+
+ const char *channelmap_scan_group =
+ hdhomerun_channelmap_get_channelmap_scan_group(channelmap);
+ if (!channelmap_scan_group) {
+ fprintf(stderr, "unknown channelmap '%s'\n", channelmap);
+ return -1;
+ }
+
+ if (hdhomerun_device_channelscan_init(hd, channelmap_scan_group) <= 0) {
+ fprintf(stderr, "failed to initialize channel scan\n");
+ return -1;
+ }
+
+ register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler);
+
+ int ret = 0;
+ while (!sigabort_flag && found_channel < ce_num) {
+ struct hdhomerun_channelscan_result_t result;
+ char tune_str[32];
+ char channel_name[32];
+ int channel_num;
+ ret = hdhomerun_device_channelscan_advance(hd, &result);
+ if (ret <= 0) {
+ break;
+ }
+
+ ret = hdhomerun_device_channelscan_detect(hd, &result);
+ if (ret < 0) {
+ break;
+ }
+ if (ret == 0) {
+ continue;
+ }
+ if (0)
+ printf("LOCK: %s %s (ss=%u snq=%u seq=%u)\n",
+ get_tune_string(result.channel_str, tune_str, sizeof(tune_str)),
+ result.status.lock_str, result.status.signal_strength,
+ result.status.signal_to_noise_quality,
+ result.status.symbol_error_quality);
+
+ channel_num = get_tune_channel(result.channel_str);
+ int i;
+ for (i = 0; i < result.program_count; i++) {
+ struct hdhomerun_channelscan_program_t *program = &result.programs[i];
+ if (strstr(program->program_str, "encrypted") == NULL) {
+ int program_id = atoi(program->program_str);
+
+ if (!get_string_by_token(program->program_str, 2, channel_name,
+ sizeof(channel_name), NULL)) {
+ if (!get_string_by_token(program->program_str, 1, channel_name,
+ sizeof(channel_name), NULL))
+ snprintf(channel_name, sizeof(channel_name), "%d.%d", channel_num,
+ i);
+ else {
+ if (!strcmp(channel_name, "0"))
+ snprintf(channel_name, sizeof(channel_name), "%d.%d", channel_num,
+ i);
+ }
+ }
+ snprintf(
+ ce->dev_tuning, sizeof(ce->dev_tuning), "%s prog:%d",
+ get_tune_string(result.channel_str, tune_str, sizeof(tune_str)),
+ program_id);
+ strncpy(ce->ch_name, channel_name, sizeof(ce->ch_name));
+ strncpy(ce->dev_name, device, sizeof(ce->dev_name));
+ ce->ch = found_channel;
+ ce->frq = result.frequency;
+ ce->av_inf[0] = 0x0;
+ ce++;
+ if (++found_channel >= ce_num)
+ break;
+
+ printf("o");
+ }
+ }
+
+ printf(".");
+ fflush(stdout);
+ }
+
+ hdhomerun_device_tuner_lockkey_release(hd);
+
+ if (ret < 0) {
+ fprintf(stderr,
+ "communication error sending request to hdhomerun device\n");
+ }
+ return found_channel;
+}
+
+struct vchan_tbl_t *create_vchan_tbl(struct hdhr_tuner_t *ht)
+{
+ struct vchan_tbl_t *vt = malloc(sizeof(struct vchan_tbl_t));
+ vt->ht = *ht;
+ vt->channel_size = 500;
+ vt->channel_num = 0;
+ vt->vchan = malloc(sizeof(struct vchan_t) * vt->channel_size);
+ memset(vt->vchan, 0x0, sizeof(struct vchan_t) * vt->channel_size);
+ return vt;
+}
+
+void release_vchan_tbl(struct vchan_tbl_t *vt)
+{
+ if (vt->vchan)
+ free(vt->vchan);
+ free(vt);
+}
+
+int grow_vhcan_tbl(struct vchan_tbl_t *vt)
+{
+ int new_channel_size = vt->channel_size + 100;
+ struct vchan_t *new_vchan_list =
+ malloc(sizeof(struct vchan_t) * new_channel_size);
+ if (new_vchan_list == NULL)
+ return 0;
+ memcpy(new_vchan_list, vt->vchan, vt->channel_size * sizeof(struct vchan_t));
+ free(vt->vchan);
+ vt->vchan = new_vchan_list;
+ vt->channel_size = new_channel_size;
+ return 1;
+}
+
+int tuner_input_sharing(struct hdhr_tuner_t *ht1, struct hdhr_tuner_t *ht2)
+{
+ if (ht1->cc_tuner == 1 && !strcmp(ht1->ip, ht2->ip))
+ return 1;
+ return 0;
+}
+
+#ifdef TEST_APP
+static int scan_channel(void *device, int index, struct channel_entry_t *ce,
+ int ce_channel_num, int ce_num)
+{
+ int found_channel = 0;
+ struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+ if (hd == NULL) {
+ printf("hdhomerun invalid handle\n");
+ return -1;
+ }
+
+ char *ret_error;
+ if (hdhomerun_device_tuner_lockkey_request(hd, &ret_error) <= 0) {
+ fprintf(stderr, "failed to lock tuner\n");
+ if (ret_error) {
+ fprintf(stderr, "%s\n", ret_error);
+ }
+ return -1;
+ }
+
+ hdhomerun_device_set_tuner_target(hd, "none");
+
+ char *channelmap;
+ if (hdhomerun_device_get_tuner_channelmap(hd, &channelmap) <= 0) {
+ fprintf(stderr, "failed to query channelmap from device\n");
+ return -1;
+ }
+
+ const char *channelmap_scan_group =
+ hdhomerun_channelmap_get_channelmap_scan_group(channelmap);
+ if (!channelmap_scan_group) {
+ fprintf(stderr, "unknown channelmap '%s'\n", channelmap);
+ return -1;
+ }
+
+ if (hdhomerun_device_channelscan_init(hd, channelmap_scan_group) <= 0) {
+ fprintf(stderr, "failed to initialize channel scan\n");
+ return -1;
+ }
+
+ register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler);
+
+ int ret = 0, i;
+ do {
+ struct hdhomerun_channelscan_result_t result;
+ char tune_str[32];
+ char channel_name[32];
+ int channel_num;
+ ret = hdhomerun_device_channelscan_at(hd, index, &result);
+ // ret = hdhomerun_device_channelscan_advance( hd, &result );
+ if (ret <= 0) {
+ break;
+ }
+ // skip duplicated channel
+ for (i = 0; i < ce_channel_num; i++) {
+ if (ce->frq == result.frequency)
+ break;
+ }
+ if (ce->frq == result.frequency)
+ break;
+ ret = hdhomerun_device_channelscan_detect(hd, &result);
+ if (ret < 0) {
+ break;
+ }
+ if (ret == 0) {
+ continue;
+ }
+
+ if (0)
+ printf("LOCK: %s %s (ss=%u snq=%u seq=%u)\n",
+ get_tune_string(result.channel_str, tune_str, sizeof(tune_str)),
+ result.status.lock_str, result.status.signal_strength,
+ result.status.signal_to_noise_quality,
+ result.status.symbol_error_quality);
+
+ channel_num = get_tune_channel(result.channel_str);
+ for (i = 0; i < result.program_count; i++) {
+ struct hdhomerun_channelscan_program_t *program = &result.programs[i];
+ if (strstr(program->program_str, "encrypted") == NULL) {
+ int program_id = atoi(program->program_str);
+
+ if (!get_string_by_token(program->program_str, 2, channel_name,
+ sizeof(channel_name), NULL)) {
+ if (!get_string_by_token(program->program_str, 1, channel_name,
+ sizeof(channel_name), NULL))
+ snprintf(channel_name, sizeof(channel_name), "%d.%d", channel_num,
+ i);
+ else {
+ if (!strcmp(channel_name, "0"))
+ snprintf(channel_name, sizeof(channel_name), "%d.%d", channel_num,
+ i);
+ }
+ }
+ struct channel_entry_t *ce_p = &ce[ce_channel_num];
+ snprintf(
+ ce->dev_tuning, sizeof(ce->dev_tuning), "%s prog:%d",
+ get_tune_string(result.channel_str, tune_str, sizeof(tune_str)),
+ program_id);
+ strncpy(ce_p->ch_name, channel_name, sizeof(ce_p->ch_name));
+ strncpy(ce_p->dev_name, device, sizeof(ce_p->dev_name));
+ ce_p->ch = found_channel + ce_channel_num;
+ ce_p->frq = result.frequency;
+ ce_p->av_inf[0] = 0x0;
+ char stream_tar[32];
+ snprintf(stream_tar, sizeof(stream_tar), "udp://%s:%u", CHANNE_SCAN_SRC,
+ CHANNEL_SCAN_PORT);
+ ret = tune_channel(hd, ce_p->dev_tuning, "", stream_tar);
+ if (ret > 0)
+ ret = parse_avinf(CHANNE_SCAN_SRC, CHANNEL_SCAN_PORT, ce_p->av_inf,
+ sizeof(ce_p->av_inf), PARSE_TIMEOUT);
+ if (ret > 0)
+ ret = strstr(ce_p->av_inf, "ENCRYPTED") == NULL &&
+ strstr(ce_p->av_inf, "NO-DATA") == NULL;
+ if (ret)
+ if (++ce_channel_num > ce_num)
+ break;
+ }
+ }
+ } while (0);
+
+ hdhomerun_device_tuner_lockkey_release(hd);
+
+ if (ret < 0) {
+ fprintf(stderr,
+ "communication error sending request to hdhomerun device\n");
+ }
+ return found_channel;
+}
+
+int scan_vchannel(void *device, int vchannel, struct channel_entry_t *ce)
+{
+ int ret;
+ struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+ if (hd == NULL) {
+ sage_log((_LOG_ERROR, 3, "hdhomerun invalid handle."));
+ return -1;
+ }
+
+ ce->state = 1;
+ ce->ch = vchannel;
+ ce->frq = 0;
+ ce->av_inf[0] = 0x0;
+ char stream_tar[32];
+ snprintf(ce->ch_name, sizeof(ce->ch_name), "%d", vchannel);
+ snprintf(ce->dev_tuning, sizeof(ce->dev_tuning), "vchan:%d", vchannel);
+ snprintf(stream_tar, sizeof(stream_tar), "udp://%s:%u", CHANNE_SCAN_SRC,
+ CHANNEL_SCAN_PORT);
+ ret = tune_channel(device, ce->dev_tuning, "", stream_tar);
+ if (ret > 0)
+ ret = parse_avinf(CHANNE_SCAN_SRC, CHANNEL_SCAN_PORT, ce->av_inf,
+ sizeof(ce->av_inf), PARSE_TIMEOUT);
+ if (ret > 0)
+ ret = strstr(ce->av_inf, "ENCRYPTED") == NULL &&
+ strstr(ce->av_inf, "NO-DATA") == NULL;
+ if (ret <= 0) {
+ ce->state = 0;
+ return 0;
+ }
+
+ return 1;
+}
+
+static int _make_channel_entry_buffer(struct channel_entry_t *ce, char *buf,
+ int size)
+{
+ int pos = 0;
+ pos += snprintf(buf + pos, size - pos, "ch:%03d ", ce->ch);
+ if (ce->ch_name[0])
+ pos += snprintf(buf + pos, size - pos, "name:%s ", ce->ch_name);
+ if (ce->src_ip[0] && ce->src_port)
+ pos += snprintf(buf + pos, size - pos, "ip:%s port:%d ", ce->src_ip,
+ ce->src_port);
+ if (ce->epg_id[0])
+ pos += snprintf(buf + pos, size - pos, "epg:%s ", ce->epg_id);
+ if (ce->dev_name[0])
+ pos += snprintf(buf + pos, size - pos, "dev:%s ", ce->dev_name);
+ if (ce->dev_tuning[0])
+ pos += snprintf(buf + pos, size - pos, "tune:%s ", ce->dev_tuning);
+ if (ce->av_inf[0])
+ pos += snprintf(buf + pos, size - pos, "av:\"%s\" ", ce->av_inf);
+ pos += snprintf(buf + pos, size - pos, "\n");
+ return pos;
+}
+
+static int _save_channel_entry(char *file_name, struct channel_entry_t *ce,
+ int num)
+{
+ int i;
+ char buf[2048];
+ FILE *fp;
+ fp = fopen(file_name, "w");
+ if (fp == NULL) {
+ // printf( "Can't open file %s to save channel table, errno:%d\n",
+ // file_name, errno );
+ return -1;
+ }
+ fputs("#Google sagetv channel table ver 1.0., maximum 2048 bytes per line\n",
+ fp);
+ fprintf(fp, "total_channel:%d\n", num);
+ for (i = 0; i < num; i++) {
+ _make_channel_entry_buffer(ce, buf, sizeof(buf));
+ fputs(buf, fp);
+ ce++;
+ }
+ fclose(fp);
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ int i, ret = 0;
+ int tuner_count = 0;
+ struct hdhr_tuner_t ht[16];
+
+ ret = discover_device(ht, 16);
+ printf("found tuners:%d\n", ret);
+ if (ret > 0) {
+ for (i = 0; i < ret; i++) {
+ // if a unit shares a source input in a device, copy data.
+ if (i > 0 && tuner_input_sharing(&ht[i - 1], &ht[i])) {
+ ht[i].cc_tuner = ht[i - 1].cc_tuner;
+ strncpy(ht[i].model, ht[i - 1].model, sizeof(ht[i].model));
+ } else
+ get_dev_model(&ht[i]);
+
+ printf("%d tuner:%s ip:%s type:%d cc:%d model:%s\n", i, ht[i].name,
+ ht[i].ip, ht[i].type, ht[i].cc_tuner, ht[i].model);
+ }
+ tuner_count = ret;
+ }
+
+ // test channel scan
+ if (0) {
+ time_t t0 = time(NULL);
+ struct channel_entry_t ce[400];
+ int channel_num = 0;
+ // channel_num = scan_all( "101007FD-0", ce, 400 );
+ for (i = 60; i <= 70; i++) {
+ ret = scan_channel("101007FD-1", i, ce, channel_num, 400);
+ if (ret < 0 || sigabort_flag)
+ break;
+ printf("find channe:%d\n", ret);
+ channel_num += ret;
+ }
+ printf("time:%ld\n", time(NULL) - t0);
+ _save_channel_entry("channel-scan.txt", ce, channel_num);
+ }
+
+ // test vchannel scan
+ if (0) {
+ struct hdhr_tuner_t *cc_ht = NULL;
+ for (i = 0; i < tuner_count; i++) {
+ if (ht[i].cc_tuner) {
+ cc_ht = &ht[i];
+ break;
+ }
+ }
+
+ if (cc_ht != NULL) {
+ time_t t0 = time(NULL);
+ struct vchan_tbl_t *vt = create_vchan_tbl(cc_ht);
+ ret = get_vchannel_list(vt);
+ if (ret > 0) {
+ int n;
+ int channel_num = 0;
+ struct channel_list_t *cl = create_channel_list(ret);
+ for (n = 1; n < vt->channel_num && channel_num < 500; n++) {
+ printf("TRACE %d %s\n", n, vt->vchan[n].guide_name);
+ ret = scan_vchannel(vt, n, cl);
+ if (ret > 0) {
+ struct channel_entry_t *ce_p = &cl->ce[channel_num];
+ snprintf(ce_p->dev_name, sizeof(ce_p->dev_name), "HDHR.%s", device,
+ sizeof(ce_p->dev_name));
+ channel_num++;
+ }
+ printf("TRACE channel %d, %d\n", channel_num, n);
+ }
+ _save_channel_entry("channel-scan.txt", cl);
+ }
+ release_vchan_tbl(vt);
+ printf("time:%ld\n", time(NULL) - t0);
+ }
+ }
+
+ // test tune
+ if (1) {
+ // printf( "tuning %s \n", device[i] );
+ char *dev_name = "1311A273-0"; //"101007FD-1";
+ // ret = tune_channel( dev_name, "map:us-cable|auto:68", "343",
+ // "udp://226.0.0.1:4096" );
+ ret = tune_channel(dev_name, "vchan:600", "", "udp://226.0.0.1:4096");
+ printf("hit enter to stop\n");
+ getchar();
+ ret = stop_channel(dev_name);
+ }
+
+ return ret;
+}
+
+#endif
diff --git a/hdhomerun_tuner.h b/hdhomerun_tuner.h
new file mode 100644
index 0000000..54791ed
--- /dev/null
+++ b/hdhomerun_tuner.h
@@ -0,0 +1,47 @@
+#ifndef _HDHOMERUN_TUNER_H_
+#define _HDHOMERUN_TUNER_H_
+
+struct hdhr_tuner_t {
+ void *hd;
+ int tuner_id; // unique id in a channel service
+ char tuner_name[20]; // external name used by plugin in a channel service
+ char name[16]; // internal used by hdhomerun device
+ char ip[16]; // device unit ip
+ char model[16]; // device model
+ int type; // device type 1:atsc 2:qam 3:dvb
+ int cc_tuner; // cable card tuner
+ void *channel_table; // channel scan private data
+};
+
+struct vchan_t {
+ int guide_id;
+ char guide_name[32];
+};
+
+struct vchan_tbl_t {
+ struct hdhr_tuner_t ht;
+ int channel_num;
+ int channel_size;
+ struct vchan_t *vchan;
+};
+
+struct channel_entry_t;
+
+int discover_device(struct hdhr_tuner_t *ht, int max_ht_num);
+void *open_hdhr(char *name);
+void close_hdhr(void *device);
+char *get_unit_name(struct hdhr_tuner_t *ht, char *name, int size);
+int scan_all(void *device, struct channel_entry_t *ce, int ce_num);
+int tune_channel(void *device, char *channel, char *program,
+ char *stream_target);
+int tune_vchannel(void *device, unsigned int channel,
+ unsigned int vchannel_major, unsigned int vchannel_minor,
+ char *stream_target);
+int stop_channel(void *device);
+int scan_vchannel(void *device, int vchannel, struct channel_entry_t *ce);
+int tuner_input_sharing(struct hdhr_tuner_t *ht1, struct hdhr_tuner_t *ht2);
+struct vchan_tbl_t *create_vchan_tbl(struct hdhr_tuner_t *ht);
+void release_vchan_tbl(struct vchan_tbl_t *vt);
+int grow_vhcan_tbl(struct vchan_tbl_t *vt);
+
+#endif
diff --git a/libhdhomerun/hdhomerun_channelscan.c b/libhdhomerun/hdhomerun_channelscan.c
index a96f925..5203280 100644
--- a/libhdhomerun/hdhomerun_channelscan.c
+++ b/libhdhomerun/hdhomerun_channelscan.c
@@ -261,6 +261,29 @@
return 1;
}
+int channelscan_at(struct hdhomerun_channelscan_t *scan, int number, struct hdhomerun_channelscan_result_t *result)
+{
+ memset(result, 0, sizeof(struct hdhomerun_channelscan_result_t));
+ struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_last( scan->channel_list );
+ while ( entry ) {
+ if ( hdhomerun_channel_entry_channel_number( entry ) == number )
+ break;
+ entry = hdhomerun_channel_list_prev(scan->channel_list, entry);
+ }
+ scan->next_channel = 0;
+
+
+ /* Combine channels with same frequency. */
+ result->frequency = hdhomerun_channel_entry_frequency(entry);
+ strncpy(result->channel_str, hdhomerun_channel_entry_name(entry), sizeof(result->channel_str) - 1);
+ result->channel_str[sizeof(result->channel_str) - 1] = 0;
+
+ char *ptr = strchr(result->channel_str, 0);
+ sprintf(ptr, ", %s", hdhomerun_channel_entry_name(entry));
+
+ return 1;
+}
+
int channelscan_detect(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result)
{
scan->scanned_channels++;
diff --git a/libhdhomerun/hdhomerun_channelscan.h b/libhdhomerun/hdhomerun_channelscan.h
index 8a1fac2..69db553 100644
--- a/libhdhomerun/hdhomerun_channelscan.h
+++ b/libhdhomerun/hdhomerun_channelscan.h
@@ -47,6 +47,7 @@
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);
+extern LIBTYPE int channelscan_at(struct hdhomerun_channelscan_t *scan, int number, struct hdhomerun_channelscan_result_t *result); //ZQ
#ifdef __cplusplus
}
diff --git a/libhdhomerun/hdhomerun_device.c b/libhdhomerun/hdhomerun_device.c
index fb6e5e0..6bd71d2 100644
--- a/libhdhomerun/hdhomerun_device.c
+++ b/libhdhomerun/hdhomerun_device.c
@@ -1286,6 +1286,22 @@
return ret;
}
+int hdhomerun_device_channelscan_at(struct hdhomerun_device_t *hd, int channel_num, struct hdhomerun_channelscan_result_t *result)
+{
+ if (!hd->scan) {
+ hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_at: scan not initialized\n");
+ return 0;
+ }
+
+ int ret = channelscan_at(hd->scan, channel_num, 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) {
diff --git a/libhdhomerun/hdhomerun_device.h b/libhdhomerun/hdhomerun_device.h
index 51c2b86..0648caa 100644
--- a/libhdhomerun/hdhomerun_device.h
+++ b/libhdhomerun/hdhomerun_device.h
@@ -239,6 +239,7 @@
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);
+extern LIBTYPE int hdhomerun_device_channelscan_at(struct hdhomerun_device_t *hd, int channel_num, struct hdhomerun_channelscan_result_t *result);
/*
* Upload new firmware to the device.
diff --git a/libhdhomerun/hdhomerun_sock_posix.c b/libhdhomerun/hdhomerun_sock_posix.c
index 74dcd7c..e59f903 100644
--- a/libhdhomerun/hdhomerun_sock_posix.c
+++ b/libhdhomerun/hdhomerun_sock_posix.c
@@ -50,19 +50,13 @@
#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);
@@ -389,20 +383,30 @@
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;
- }
+ if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == 0) {
+ return TRUE;
}
uint64_t stop_time = getcurrenttime() + timeout;
+ /*
+ * getpeername() is used to detect if connect succeeded. Bug - cygwin
+ * will return getpeername success even if the connect process hasn't
+ * completed. This first call to select is used to work around the
+ * problem (at least for low numbered sockets where select is used).
+ */
+ if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+ return FALSE;
+ }
+
while (1) {
- if (send(sock, NULL, 0, MSG_NOSIGNAL) == 0) {
+ struct sockaddr_in sock_addr;
+ socklen_t sockaddr_size = sizeof(sock_addr);
+ if (getpeername(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) == 0) {
return TRUE;
}
- if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS) && (errno != ENOTCONN)) {
+ if (errno != ENOTCONN) {
return FALSE;
}
@@ -418,24 +422,26 @@
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;
+ int ret = send(sock, ptr, length, 0);
+ if (ret >= (int)length) {
+ return TRUE;
}
- if (ret < (int)length) {
+ if (ret > 0) {
ptr += ret;
length -= ret;
- continue;
}
- return TRUE;
+ if (errno == EINPROGRESS) {
+ errno = EWOULDBLOCK;
+ }
+ if (errno != EWOULDBLOCK) {
+ return FALSE;
+ }
+
+ if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+ return FALSE;
+ }
}
}
@@ -452,23 +458,25 @@
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) {
+ return TRUE;
}
- if (ret < (int)length) {
+ if (ret > 0) {
ptr += ret;
length -= ret;
- continue;
}
- return TRUE;
+ if (errno == EINPROGRESS) {
+ errno = EWOULDBLOCK;
+ }
+ if (errno != EWOULDBLOCK) {
+ return FALSE;
+ }
+
+ if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+ return FALSE;
+ }
}
}
@@ -478,22 +486,21 @@
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) {
+ *length = ret;
+ return TRUE;
}
- if (ret == 0) {
+ if (errno == EINPROGRESS) {
+ errno = EWOULDBLOCK;
+ }
+ if (errno != EWOULDBLOCK) {
return FALSE;
}
- *length = ret;
- return TRUE;
+ if (!hdhomerun_sock_wait_for_read_event(sock, stop_time)) {
+ return FALSE;
+ }
}
}
@@ -507,23 +514,22 @@
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) {
+ *remote_addr = ntohl(sock_addr.sin_addr.s_addr);
+ *remote_port = ntohs(sock_addr.sin_port);
+ *length = ret;
+ return TRUE;
}
- if (ret == 0) {
+ if (errno == EINPROGRESS) {
+ errno = EWOULDBLOCK;
+ }
+ if (errno != EWOULDBLOCK) {
return FALSE;
}
- *remote_addr = ntohl(sock_addr.sin_addr.s_addr);
- *remote_port = ntohs(sock_addr.sin_port);
- *length = ret;
- return TRUE;
+ if (!hdhomerun_sock_wait_for_read_event(sock, stop_time)) {
+ return FALSE;
+ }
}
}
diff --git a/libhdhomerun/hdhomerun_sock_windows.c b/libhdhomerun/hdhomerun_sock_windows.c
index 4eb0c45..7bb14f5 100644
--- a/libhdhomerun/hdhomerun_sock_windows.c
+++ b/libhdhomerun/hdhomerun_sock_windows.c
@@ -268,6 +268,16 @@
bool_t hdhomerun_sock_connect(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout)
{
+ WSAEVENT wsa_event = WSACreateEvent();
+ if (wsa_event == WSA_INVALID_EVENT) {
+ return FALSE;
+ }
+
+ if (WSAEventSelect(sock, wsa_event, FD_CONNECT) == SOCKET_ERROR) {
+ WSACloseEvent(wsa_event);
+ return FALSE;
+ }
+
/* Connect (non-blocking). */
struct sockaddr_in sock_addr;
memset(&sock_addr, 0, sizeof(sock_addr));
@@ -277,41 +287,22 @@
if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) {
if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ WSACloseEvent(wsa_event);
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) {
+ int sockaddr_size = sizeof(sock_addr);
+ if (getpeername(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) {
return FALSE;
}
@@ -352,23 +343,22 @@
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) {
+ return TRUE;
}
- if (ret < (int)length) {
+ if (ret > 0) {
ptr += ret;
length -= ret;
- continue;
}
- return TRUE;
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ return FALSE;
+ }
+
+ if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) {
+ return FALSE;
+ }
}
}
@@ -385,23 +375,22 @@
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) {
+ return TRUE;
}
- if (ret < (int)length) {
+ if (ret > 0) {
ptr += ret;
length -= ret;
- continue;
}
- return TRUE;
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ return FALSE;
+ }
+
+ if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) {
+ return FALSE;
+ }
}
}
@@ -411,22 +400,18 @@
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) {
+ *length = ret;
+ return TRUE;
}
- if (ret == 0) {
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
return FALSE;
}
- *length = ret;
- return TRUE;
+ if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) {
+ return FALSE;
+ }
}
}
@@ -440,23 +425,19 @@
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) {
+ *remote_addr = ntohl(sock_addr.sin_addr.s_addr);
+ *remote_port = ntohs(sock_addr.sin_port);
+ *length = ret;
+ return TRUE;
}
- if (ret == 0) {
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
return FALSE;
}
- *remote_addr = ntohl(sock_addr.sin_addr.s_addr);
- *remote_port = ntohs(sock_addr.sin_port);
- *length = ret;
- return TRUE;
+ if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) {
+ return FALSE;
+ }
}
}
diff --git a/sagelog.c b/sagelog.c
new file mode 100644
index 0000000..d0e7038
--- /dev/null
+++ b/sagelog.c
@@ -0,0 +1,104 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (ken Zhang)
+
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include "sagelog.h"
+
+int sagelog_enabled = 1;
+int console_enabled = 1;
+int trace_level = 3;
+char log_filename[128] = "/tmp/SageEncoder.log";
+
+void _enable_native_log(void)
+{
+ sagelog_enabled = 1;
+}
+
+void _disable_native_log(void)
+{
+ sagelog_enabled = 0;
+}
+
+void _set_native_log(int level)
+{
+ if (level == 0) {
+ sagelog_enabled = 0;
+ printf("sagelog is disabled.");
+ } else {
+ sagelog_enabled = 1;
+ trace_level = level;
+ printf("sagelog is enabled, \"%s\"\n", log_filename);
+ }
+}
+
+void _sagelog(int type, int level, const char *cstr, ...)
+{
+ time_t ct;
+ struct tm ltm;
+ va_list args;
+ char szInfo[1024 * 3];
+ va_list va;
+ FILE *fp;
+
+ if (!sagelog_enabled)
+ return;
+ if (type == _LOG_TRACE && level > trace_level)
+ return;
+
+ fp = fopen(log_filename, "a");
+ if (fp == NULL)
+ return;
+ time(&ct);
+ localtime_r(&ct, <m);
+
+ va_start(va, cstr);
+
+ snprintf(szInfo, sizeof(szInfo) - 3, TEXT("%02d/%02d/%d %02d:%02d:%02d "),
+ ltm.tm_mon + 1, ltm.tm_mday, ltm.tm_year + 1900, ltm.tm_hour,
+ ltm.tm_min, ltm.tm_sec);
+
+ vsnprintf(szInfo + lstrlen(szInfo), (int)sizeof(szInfo) - lstrlen(szInfo) - 3,
+ cstr, va);
+ lstrcat(szInfo, TEXT("\r\n"));
+ fwrite((char *)szInfo, 1, lstrlen(szInfo), fp);
+
+ if (console_enabled)
+ printf("%s", szInfo);
+
+ va_end(args);
+ fclose(fp);
+}
+
+void _flog_setup(char *filename, int enable)
+{
+ if (filename != NULL && filename[0])
+ strncpy(log_filename, filename, sizeof(log_filename));
+
+ sagelog_enabled = enable;
+}
+
+char *_get_flog_path(void)
+{
+ return log_filename;
+}
+
+int _get_flog_state(void)
+{
+ return sagelog_enabled;
+}
+
+int _flog_check(void)
+{
+ FILE *fp = fopen("NATIVE_LOG.ENABLE", "r");
+ if (fp != NULL) {
+ sagelog_enabled = 1;
+ fclose(fp);
+ return 1;
+ }
+ return 0;
+}
diff --git a/sagelog.h b/sagelog.h
new file mode 100644
index 0000000..164ba9d
--- /dev/null
+++ b/sagelog.h
@@ -0,0 +1,52 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (ken Zhang)
+
+#ifndef _SAGELOG_H_
+#define _SAGELOG_H_
+
+#include <assert.h>
+
+enum {
+ _LOG_TIMING = 0x01, // Timing and performance measurements
+ _LOG_TRACE = 0x02, // General step point call tracing
+ _LOG_MEMORY = 0x04, // Memory and object allocation/destruction
+ _LOG_LOCKING = 0x08, // Locking/unlocking of critical sections
+ _LOG_ERROR = 0x10, // Debug error notification
+ _LOG_CUSTOM1 = 0x20,
+ _LOG_CUSTOM2 = 0x40,
+};
+
+#define _MIN(x, y) ((x) > (y) ? (y) : (x))
+#define _MAX(x, y) ((x) > (y) ? (x) : (y))
+
+#ifndef TEXT
+#define TEXT(x) x
+#endif
+
+#define snprintf snprintf
+#define vsnprintf vsnprintf
+
+#ifndef lstrlen
+#define lstrlen strlen
+#endif
+
+#ifndef lstrcat
+#define lstrcat strcat
+#endif
+
+#ifndef ASSERT
+#define ASSERT assert
+#endif
+
+#define sage_log(x) _sagelog x
+
+void _sagelog(int type, int level, const char *cstr, ...);
+void _flog_setup(char *filename, int enable);
+char *_get_flog_path(void);
+int _get_flog_state(void);
+int _flog_check(void);
+void _set_native_log(int level);
+void _enable_native_log(void);
+void _disable_native_log(void);
+
+#endif
diff --git a/utility.c b/utility.c
new file mode 100644
index 0000000..5770319
--- /dev/null
+++ b/utility.c
@@ -0,0 +1,237 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (ken Zhang)
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "channel_buffer.h"
+#include "utility.h"
+
+static const char *delimiter = " \t,;\r\n:=|'\"#";
+
+static int is_delimiter(char ch)
+{
+ int i;
+ if (ch == 0)
+ return 1;
+ for (i = 0; delimiter[i]; i++)
+ if (ch == delimiter[i])
+ return 1;
+ return 0;
+}
+
+// format name:zzz (or name=zzzz)
+int get_string_by_name(char *p, const char *name, char *string, int max_len,
+ char **next)
+{
+ char *s, *e;
+ e = p;
+
+ while (e != NULL && *e) {
+ s = strstr(e, name);
+ if (s == NULL)
+ return 0;
+ // is a token name
+ if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+ is_delimiter(*(s + strlen(name)))) {
+ s += (int)strlen(name);
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s == '=' || *s == ':') {
+ s++;
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s) {
+ int i = 0;
+ while (i++ < max_len && *s && *s != ' ' && *s != '\t' && *s != '\n' &&
+ *s != '\r')
+ *string++ = *s++;
+
+ *string = 0x0; // terminator
+ if (next != NULL) {
+ while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r')
+ s++;
+ *next = s;
+ }
+ return 1;
+ }
+ }
+ }
+ e = ++s;
+ }
+ return 0;
+}
+
+// format name:zzz (or name=zzzz)
+int get_string_by_token(char *p, int num, char *string, int max_len,
+ char **next)
+{
+ int count = 0;
+ char *s;
+ s = p;
+ if (s == NULL)
+ return 0;
+ while (*s) {
+ while (*s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
+ s++; // skip white space
+ if (*s == 0)
+ break;
+
+ if (count++ != num) {
+ while (*s && !(*s == ' ' || *s == '\t' || *s == ';' || *s == ',' ||
+ *s == '\r' || *s == '\n'))
+ s++; // skip a token
+ } else {
+ if (*s) {
+ int i = 0;
+ while (i++ < max_len && *s && !(*s == ' ' || *s == '\t' || *s == ';' ||
+ *s == ',' || *s == '\r' || *s == '\n'))
+ *string++ = *s++;
+
+ *string = 0x0; // terminator
+ if (next != NULL) {
+ while (*s && !(*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
+ s++;
+ *next = s;
+ }
+ return 1;
+ }
+ }
+ if (*s)
+ s++;
+ }
+ return 0;
+}
+
+// format name:"zzz" (or name="zzzz")
+int get_quote_string_by_name(char *p, const char *name, char *string,
+ int max_len, char **next)
+{
+ char *s, *e;
+ e = p;
+
+ while (e != NULL && *e) {
+ s = strstr(e, name);
+ if (s == NULL)
+ return 0;
+ // is a token name
+ if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+ is_delimiter(*(s + strlen(name)))) {
+ s += (int)strlen(name);
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s == '=' || *s == ':') {
+ s++;
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s == '\"') {
+ int i = 0;
+ s++;
+ while (i++ < max_len && *s && *s != '\"') *string++ = *s++;
+
+ *string = 0x0; // terminator
+
+ if (next != NULL) {
+ while (*s && *s != '\"') s++;
+ *next = s + 1;
+ }
+ return 1;
+ } else
+ return 0;
+ }
+ }
+ e = ++s;
+ }
+ return 0;
+}
+
+int get_int_val_by_name(char *p, const char *name, int *val, char **next)
+{
+ char *s, *e;
+ e = p;
+ while (e != NULL && *e) {
+ s = strstr(e, name);
+ if (s == NULL)
+ return 0;
+ // is a token name
+ if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+ is_delimiter(*(s + strlen(name)))) {
+ s += strlen(name);
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s == '=' || *s == ':') {
+ s++;
+ while (*s && (*s == ' ' || *s == '\t')) s++; // skip white space
+ if (*s) {
+ *val = atoi(s);
+ if (*val < 0 && *s == '-')
+ s++;
+ while (*s >= '0' && *s <= '9') s++;
+ if (next != NULL)
+ *next = s;
+ return 1;
+ }
+ }
+ }
+ e = ++s;
+ }
+ return 0;
+}
+
+struct channel_list_t *_create_channel_list(int num)
+{
+ if (num <= 0)
+ return NULL;
+ struct channel_list_t *cl = malloc(sizeof(struct channel_list_t));
+ if (cl == NULL)
+ return NULL;
+ cl->num = num;
+ cl->channel_num = 0;
+ cl->ce = malloc(sizeof(struct channel_entry_t) * num);
+ if (cl->ce == NULL) {
+ free(cl);
+ return NULL;
+ }
+ memset(cl->ce, 0x0, sizeof(struct channel_entry_t) * num);
+ return cl;
+}
+
+void _release_channel_list(struct channel_list_t *cl)
+{
+ if (cl) {
+ if (cl->ce)
+ free(cl->ce);
+ free(cl);
+ }
+}
+
+struct channel_list_t *_joint_channel_list(struct channel_list_t *cl1,
+ struct channel_list_t *cl2)
+{
+ int i, n, channel_num;
+ if (cl1 == NULL)
+ return cl2;
+ if (cl2 == NULL)
+ return cl1;
+
+ channel_num = cl1->channel_num + cl2->channel_num;
+ if (channel_num < cl1->num) {
+ n = 0;
+ for (i = cl1->channel_num; i < channel_num; i++) cl1->ce[i] = cl2->ce[n++];
+ cl2->channel_num = channel_num;
+ _release_channel_list(cl2);
+ return cl1;
+ } else if (channel_num < cl2->num) {
+ n = 0;
+ for (i = cl2->channel_num; i < channel_num; i++) cl2->ce[i] = cl1->ce[n++];
+ cl2->channel_num = channel_num;
+ _release_channel_list(cl1);
+ return cl2;
+ } else {
+ struct channel_list_t *cl = _create_channel_list(channel_num);
+ for (i = 0; i < cl1->channel_num; i++) cl->ce[i] = cl1->ce[i];
+ for (n = 0; n < cl2->channel_num; n++) cl->ce[i++] = cl2->ce[n];
+ _release_channel_list(cl1);
+ _release_channel_list(cl2);
+ cl->channel_num = channel_num;
+ return cl;
+ }
+ return NULL;
+}
diff --git a/utility.h b/utility.h
new file mode 100644
index 0000000..b62b9ce
--- /dev/null
+++ b/utility.h
@@ -0,0 +1,17 @@
+#ifndef _UTILITY_H_
+#define _UTILITY_H_
+
+int get_string_by_name(char *p, const char *name, char *string, int max_len,
+ char **next);
+int get_string_by_token(char *p, int num, char *string, int max_len,
+ char **next);
+int get_quote_string_by_name(char *p, const char *name, char *string,
+ int max_len, char **next);
+int get_int_val_by_name(char *p, const char *name, int *val, char **next);
+
+struct channel_list_t *_create_channel_list(int num);
+void _release_channel_list(struct channel_list_t *cl);
+struct channel_list_t *_joint_channel_list(struct channel_list_t *cl1,
+ struct channel_list_t *cl2);
+
+#endif