This is merge of PR#341 (esfilter) and PR#369 (SAT>IP)

This commit is contained in:
Jaroslav Kysela 2014-05-07 14:32:08 +02:00
commit de20cfda59
63 changed files with 8593 additions and 628 deletions

View file

@ -76,6 +76,7 @@ SRCS = src/version.c \
src/access.c \
src/dtable.c \
src/tcp.c \
src/udp.c \
src/url.c \
src/http.c \
src/notify.c \
@ -112,9 +113,14 @@ SRCS = src/version.c \
src/descrambler/descrambler.c \
src/service_mapper.c \
src/input.c \
src/http/http_client.c \
src/httpc.c \
src/rtsp.c \
src/fsmonitor.c \
src/cron.c \
src/esfilter.c
SRCS-${CONFIG_UPNP} += \
src/upnp.c
SRCS += \
src/api.c \
@ -126,7 +132,8 @@ SRCS += \
src/api/api_mpegts.c \
src/api/api_epg.c \
src/api/api_epggrab.c \
src/api/api_imagecache.c
src/api/api_imagecache.c \
src/api/api_esfilter.c
SRCS += \
src/parsers/parsers.c \
@ -205,6 +212,13 @@ SRCS-${CONFIG_LINUXDVB} += \
src/input/mpegts/linuxdvb/linuxdvb_rotor.c \
src/input/mpegts/linuxdvb/linuxdvb_en50494.c
# SATIP
SRCS-${CONFIG_SATIP_CLIENT} += \
src/input/mpegts/satip/satip.c \
src/input/mpegts/satip/satip_frontend.c \
src/input/mpegts/satip/satip_satconf.c \
src/input/mpegts/satip/satip_rtsp.c
# IPTV
SRCS-${CONFIG_IPTV} += \
src/input/mpegts/iptv/iptv.c \

30
configure vendored
View file

@ -19,6 +19,7 @@ OPTIONS=(
"cwc:yes"
"v4l:no"
"linuxdvb:yes"
"satip_client:yes"
"iptv:yes"
"tsfile:yes"
"dvbscan:yes"
@ -29,7 +30,6 @@ OPTIONS=(
"zlib:auto"
"libav:auto"
"inotify:auto"
"curl:auto"
"epoll:auto"
"uriparser:auto"
"ccache:auto"
@ -113,6 +113,18 @@ int test(void)
return 0;
}
'
check_cc_snippet recvmmsg '
#define _GNU_SOURCE
#include <stdlib.h>
#include <sys/socket.h>
#define TEST test
int test(void)
{
recvmmsg(0, NULL, 0, 0, NULL);
return 0;
}
'
#
# Python
#
@ -161,14 +173,10 @@ if enabled_or_auto zlib; then
fi
#
# CURL
# SAT>IP client
#
if enabled_or_auto curl; then
if check_pkg libcurl; then
enable curl
elif enabled curl; then
die "Curl development support not foun (use --disable-curl)"
fi
if enabled_or_auto satip_client; then
enable upnp
fi
#
@ -264,11 +272,7 @@ fi
# Icon caching
#
if enabled_or_auto imagecache; then
if enabled curl; then
enable imagecache
elif enabled imagecache; then
die "Libcurl support not found (use --disable-imagecache)"
fi
enable imagecache
fi
#

View file

@ -0,0 +1,109 @@
<div class="hts-doc-text">
This table defines rules to filter and order the elementary streams
like video or audio from the input feed.
<p>
The execution order of commands is granted. It means that first rule
is executed for all available streams then second and so on.
<p>
If any elementary stream is not marked as ignored or exclusive, it is
used. If you like to ignore unknown elementary streams, add a rule
to the end of grid with the any (not defined) comparisons and
with the action ignore.
<p>
The rules for different elementary stream groups (video, audio,
teletext, subtitle, CA, other) are executed separately (as visually edited).
<p>
For the visual verification of the filtering, there is a service info
dialog in the Configuration / DVB Inputs / Services window . This dialog
shows the received PIDs and filtered PIDs in one window.
<p>
The rules are listed / edited in a grid.
<ul>
<li>To edit a cell, double click on it. After a cell is changed it
will flags one of its corner to red to indicated that it has been
changed. To commit these changes back to Tvheadend press the
'Save changes' button. In order to change a Checkbox cell you only
have to click once in it.
<li>To add a new entry, press the 'Add entry' button. The new (empty) entry
will be created on the server but will not be in its enabled state.
You can now change all the cells to the desired values, check the
'enable' box and then press 'Save changes' to activate the new entry.
<li>To delete one or more entries, select the lines (by clicking once on
them), and press the 'Delete selected' button. A pop up
will ask you to confirm your request.
<li>To move up or down one or more entries, select the lines (by clicking
once on them), and press the 'Move up' or 'Move down' button.
</ul>
<p>
The columns have the following functions:
<dl>
<dt>Enabled
<dd>If selected, the rule will be enabled.
<dt>Stream Type
<dd>Select the elementary stream type to compare. Empty field means any.
<dt>Language
<dd>Select the language to compare. Empty field means any.
<dt>Service
<dd>The service to compare. Empty field means any.
<dt>CA Identification
<dd>The CAID to compare. Empty field means any.
<dt>CA Provider
<dd>The CA provider to compare. Empty field means any.
<dt>PID
<dd>Program identification (PID) number to compare. Zero means any.
This comparison is processed only when service comparison is active.
<dt>Action
<dd>The rule action defines the operation when all comparisons succeeds.
<dl>
<dt>NONE
<dd>No action, may be used for the logging and a comparison verification.
<dt>USE
<dd>Use this elementary stream.
<dt>ONCE
<dd>Use this elementary stream only once per selected language.
The first successfully compared rule wins.
<dt>EXCLUSIVE
<dd>Use only this elementary stream. No other elementary streams
will be used.
<dt>EMPTY
<dd>Add this elementary stream only when no elementary streams are
used from previous rules.
<dt>IGNORE
<dd>Ignore this elementary stream. This stream is not used. Another
successfully compared rule with different action may override it.
</dl>
<dt>Log
<dd>Write a short message to log identifying the matched parameters.
It is useful for debugging your setup or structure of incoming
streams.
</dl>
</div>

View file

@ -0,0 +1,75 @@
<div class="hts-doc-text">
<h3>The adapters and tuners are listed / edited in a tree</h3>
<ul>
<li>To edit an item, click on it. To commit edited changes back to
Tvheadend press the 'Save' button. In order to change a Checkbox cell
you only have to click once in it.
</ul>
</p>
<h4>The rows have the following functions</h4>
<dl>
<dt>Enabled</dt>
<dd>If selected, the IPTV service will be enabled an use for channel
subscriptions.</dd>
<dt>Name</dt>
<dd>The name of this tuner.</dd>
</dl>
</p>
<h4>LinuxDVB specific rows</h4>
<dl>
<dt>Keep FE open</dt>
<dd>Enable to not close the LinuxDVB frontend device in the idle state.</dd>
</dl>
<h4>SAT>IP specific rows</h4>
<dl>
<dt>Full Mux Rx mode supported</dt>
<dd>Enable, if the SAT>IP box supports the full mux rx mode (pids=all)
parameter.</dd>
<dt>Signal scale (240 or 100)</dt>
<dd>Some SAT>IP boxes has only 0-100 scale. If your signal is too low, try
value 100 here.</dd>
<dt>Maximum PIDs<dt>
<dd>Maximum supported PIDs in the filter of the SAT>IP box.</dd>
<dt>Maximum length of PIDs<dt>
<dd>Maximum length in characters for the command setting PIDs to the
SAT>IP box.</dd>
<dt>addpids/delpids supported</dt>
<dd>Enable, if the SAT>IP box supports the addpids/delpids command.
<dt>PIDs in setup</dt>
<dd>Enable, if the SAT>IP box requires pids=0 parameter in the SETUP RTSP command.</dd>
<dt>UDP RTP Port Number (2 ports)</dt>
<dd>Force the local UDP Port number here. The number should be even (RTP port).
The next odd number (+1) will be used as the RTCP port.</dd>
<dt>Satellite Positions</dt>
<dd>Select the number of satellite positions supported by the SAT>IP
hardware and your coaxial cable wiring.</dd>
<dt>Master Tuner</dt>
<dd>Select the master tuner.
<p>The signal from the standard universal LNB can be split using
a simple coaxial splitter (no multiswitch) to several outputs.
In this case, the position, the polarization and low-high
band settings must be equal.</p>
<p>If you set other tuner as master, then this tuner will act like
a slave one and tvheadend will assure that this tuner will not
use incompatible parameters (position, polarization, lo-hi).</p>
</dd>
</dl>
</div>

View file

@ -125,6 +125,7 @@ void api_init ( void )
api_epggrab_init();
api_status_init();
api_imagecache_init();
api_esfilter_init();
}
void api_done ( void )

View file

@ -59,6 +59,7 @@ void api_init ( void );
void api_done ( void );
void api_idnode_init ( void );
void api_input_init ( void );
void api_input_satip_init ( void );
void api_service_init ( void );
void api_channel_init ( void );
void api_mpegts_init ( void );
@ -66,12 +67,14 @@ void api_epg_init ( void );
void api_epggrab_init ( void );
void api_status_init ( void );
void api_imagecache_init ( void );
void api_esfilter_init ( void );
/*
* IDnode
*/
typedef struct api_idnode_grid_conf
{
int tindex;
int start;
int limit;
idnode_filter_t filter;

101
src/api/api_esfilter.c Normal file
View file

@ -0,0 +1,101 @@
/*
* API - elementary stream filter related calls
*
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tvheadend.h"
#include "esfilter.h"
#include "lang_codes.h"
#include "access.h"
#include "api.h"
static void
api_esfilter_grid
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args,
esfilter_class_t cls )
{
esfilter_t *esf;
TAILQ_FOREACH(esf, &esfilters[cls], esf_link) {
idnode_set_add(ins, (idnode_t*)esf, &conf->filter);
}
}
static int
api_esfilter_create
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp,
esfilter_class_t cls )
{
htsmsg_t *conf;
if (!(conf = htsmsg_get_map(args, "conf")))
return EINVAL;
pthread_mutex_lock(&global_lock);
esfilter_create(cls, NULL, conf, 1);
pthread_mutex_unlock(&global_lock);
return 0;
}
#define ESFILTER(func, t) \
static void api_esfilter_grid_##func \
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args ) \
{ return api_esfilter_grid(ins, conf, args, (t)); } \
static int api_esfilter_create_##func \
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) \
{ return api_esfilter_create(opaque, op, args, resp, (t)); }
ESFILTER(video, ESF_CLASS_VIDEO);
ESFILTER(audio, ESF_CLASS_AUDIO);
ESFILTER(teletext, ESF_CLASS_TELETEXT);
ESFILTER(subtit, ESF_CLASS_SUBTIT);
ESFILTER(ca, ESF_CLASS_CA);
ESFILTER(other, ESF_CLASS_OTHER);
void api_esfilter_init ( void )
{
static api_hook_t ah[] = {
{ "esfilter/video/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_video },
{ "esfilter/video/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_video },
{ "esfilter/video/create", ACCESS_ADMIN, api_esfilter_create_video, NULL },
{ "esfilter/audio/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_audio },
{ "esfilter/audio/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_audio },
{ "esfilter/audio/create", ACCESS_ADMIN, api_esfilter_create_audio, NULL },
{ "esfilter/teletext/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_teletext },
{ "esfilter/teletext/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_teletext },
{ "esfilter/teletext/create",ACCESS_ADMIN, api_esfilter_create_teletext, NULL },
{ "esfilter/subtit/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_subtit },
{ "esfilter/subtit/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_subtit },
{ "esfilter/subtit/create", ACCESS_ADMIN, api_esfilter_create_subtit, NULL },
{ "esfilter/ca/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_ca },
{ "esfilter/ca/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_ca },
{ "esfilter/ca/create", ACCESS_ADMIN, api_esfilter_create_ca, NULL },
{ "esfilter/other/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_other },
{ "esfilter/other/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_other },
{ "esfilter/other/create", ACCESS_ADMIN, api_esfilter_create_other, NULL },
{ NULL },
};
api_register_all(ah);
}

View file

@ -377,8 +377,8 @@ exit:
}
static int
api_idnode_delete
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
api_idnode_handler
( htsmsg_t *args, htsmsg_t **resp, void (*handler)(idnode_t *in) )
{
int err = 0;
idnode_t *in;
@ -400,7 +400,7 @@ api_idnode_delete
HTSMSG_FOREACH(f, uuids) {
if (!(uuid = htsmsg_field_get_string(f))) continue;
if (!(in = idnode_find(uuid, NULL))) continue;
idnode_delete(in);
handler(in);
}
/* Single */
@ -409,7 +409,7 @@ api_idnode_delete
if (!(in = idnode_find(uuid, NULL)))
err = ENOENT;
else
idnode_delete(in);
handler(in);
}
pthread_mutex_unlock(&global_lock);
@ -417,14 +417,37 @@ api_idnode_delete
return err;
}
static int
api_idnode_delete
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
return api_idnode_handler(args, resp, idnode_delete);
}
static int
api_idnode_moveup
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
return api_idnode_handler(args, resp, idnode_moveup);
}
static int
api_idnode_movedown
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
return api_idnode_handler(args, resp, idnode_movedown);
}
void api_idnode_init ( void )
{
static api_hook_t ah[] = {
{ "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL },
{ "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL },
{ "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL },
{ "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL },
{ "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL },
{ "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL },
{ "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL },
{ "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL },
{ "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL },
{ "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL },
{ "idnode/moveup", ACCESS_ADMIN, api_idnode_moveup, NULL },
{ "idnode/movedown", ACCESS_ADMIN, api_idnode_movedown, NULL },
{ NULL },
};

View file

@ -92,12 +92,45 @@ api_service_mapper_notify ( void )
notify_by_msg("servicemapper", api_mapper_status_msg());
}
static htsmsg_t *
api_service_streams_get_one ( elementary_stream_t *es )
{
htsmsg_t *e = htsmsg_create_map();
htsmsg_add_u32(e, "index", es->es_index);
htsmsg_add_u32(e, "pid", es->es_pid);
htsmsg_add_str(e, "type", streaming_component_type2txt(es->es_type));
htsmsg_add_str(e, "language", es->es_lang);
if (SCT_ISSUBTITLE(es->es_type)) {
htsmsg_add_u32(e, "composition_id", es->es_composition_id);
htsmsg_add_u32(e, "ancillary_id", es->es_ancillary_id);
} else if (SCT_ISAUDIO(es->es_type)) {
htsmsg_add_u32(e, "audio_type", es->es_audio_type);
} else if (SCT_ISVIDEO(es->es_type)) {
htsmsg_add_u32(e, "width", es->es_width);
htsmsg_add_u32(e, "height", es->es_height);
htsmsg_add_u32(e, "duration", es->es_frame_duration);
htsmsg_add_u32(e, "aspect_num", es->es_aspect_num);
htsmsg_add_u32(e, "aspect_den", es->es_aspect_den);
} else if (es->es_type == SCT_CA) {
caid_t *ca;
htsmsg_t *e2, *l2 = htsmsg_create_list();
LIST_FOREACH(ca, &es->es_caids, link) {
e2 = htsmsg_create_map();
htsmsg_add_u32(e2, "caid", ca->caid);
htsmsg_add_u32(e2, "provider", ca->providerid);
htsmsg_add_msg(l2, NULL, e2);
}
htsmsg_add_msg(e, "caids", l2);
}
return e;
}
static int
api_service_streams
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
const char *uuid;
htsmsg_t *e, *st;
htsmsg_t *e, *st, *stf;
service_t *s;
elementary_stream_t *es;
@ -116,6 +149,7 @@ api_service_streams
/* Build response */
pthread_mutex_lock(&s->s_stream_mutex);
st = htsmsg_create_list();
stf = htsmsg_create_list();
if (s->s_pcr_pid) {
e = htsmsg_create_map();
htsmsg_add_u32(e, "pid", s->s_pcr_pid);
@ -128,39 +162,17 @@ api_service_streams
htsmsg_add_str(e, "type", "PMT");
htsmsg_add_msg(st, NULL, e);
}
TAILQ_FOREACH(es, &s->s_components, es_link) {
htsmsg_t *e = htsmsg_create_map();
htsmsg_add_u32(e, "index", es->es_index);
htsmsg_add_u32(e, "pid", es->es_pid);
htsmsg_add_str(e, "type", streaming_component_type2txt(es->es_type));
htsmsg_add_str(e, "language", es->es_lang);
if (SCT_ISSUBTITLE(es->es_type)) {
htsmsg_add_u32(e, "composition_id", es->es_composition_id);
htsmsg_add_u32(e, "ancillary_id", es->es_ancillary_id);
} else if (SCT_ISAUDIO(es->es_type)) {
htsmsg_add_u32(e, "audio_type", es->es_audio_type);
} else if (SCT_ISVIDEO(es->es_type)) {
htsmsg_add_u32(e, "width", es->es_width);
htsmsg_add_u32(e, "height", es->es_height);
htsmsg_add_u32(e, "duration", es->es_frame_duration);
htsmsg_add_u32(e, "aspect_num", es->es_aspect_num);
htsmsg_add_u32(e, "aspect_den", es->es_aspect_den);
} else if (es->es_type == SCT_CA) {
caid_t *ca;
htsmsg_t *e2, *l2 = htsmsg_create_list();
LIST_FOREACH(ca, &es->es_caids, link) {
e2 = htsmsg_create_map();
htsmsg_add_u32(e2, "caid", ca->caid);
htsmsg_add_u32(e2, "provider", ca->providerid);
htsmsg_add_msg(l2, NULL, e2);
}
htsmsg_add_msg(e, "caids", l2);
}
htsmsg_add_msg(st, NULL, e);
}
TAILQ_FOREACH(es, &s->s_components, es_link)
htsmsg_add_msg(st, NULL, api_service_streams_get_one(es));
if (TAILQ_FIRST(&s->s_filt_components) == NULL ||
s->s_status == SERVICE_IDLE)
service_build_filter(s);
TAILQ_FOREACH(es, &s->s_filt_components, es_filt_link)
htsmsg_add_msg(stf, NULL, api_service_streams_get_one(es));
*resp = htsmsg_create_map();
htsmsg_add_str(*resp, "name", s->s_nicename);
htsmsg_add_msg(*resp, "streams", st);
htsmsg_add_msg(*resp, "fstreams", stf);
pthread_mutex_unlock(&s->s_stream_mutex);
/* Done */

1023
src/esfilter.c Normal file

File diff suppressed because it is too large Load diff

110
src/esfilter.h Normal file
View file

@ -0,0 +1,110 @@
/*
* tvheadend, Elementary Stream Filter
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TVH_ESFILTER_H__
#define __TVH_ESFILTER_H__
#include "tvheadend.h"
#include "idnode.h"
typedef enum {
ESF_CLASS_NONE = 0,
ESF_CLASS_VIDEO,
ESF_CLASS_AUDIO,
ESF_CLASS_TELETEXT,
ESF_CLASS_SUBTIT,
ESF_CLASS_CA,
ESF_CLASS_OTHER,
ESF_CLASS_LAST = ESF_CLASS_OTHER
} esfilter_class_t;
#define ESF_CLASS_IS_VALID(i) \
((i) >= ESF_CLASS_VIDEO && (i) <= ESF_CLASS_LAST)
extern const idclass_t esfilter_class;
extern const idclass_t esfilter_class_video;
extern const idclass_t esfilter_class_audio;
extern const idclass_t esfilter_class_teletext;
extern const idclass_t esfilter_class_subtit;
extern const idclass_t esfilter_class_ca;
extern const idclass_t esfilter_class_other;
#define ESF_MASK_VIDEO \
(SCT_MASK(SCT_MPEG2VIDEO) | SCT_MASK(SCT_H264) | SCT_MASK(SCT_VP8))
#define ESF_MASK_AUDIO \
(SCT_MASK(SCT_MPEG2AUDIO) | SCT_MASK(SCT_AC3) | SCT_MASK(SCT_AAC) | \
SCT_MASK(SCT_EAC3) | SCT_MASK(SCT_MP4A) | SCT_MASK(SCT_VORBIS))
#define ESF_MASK_TELETEXT \
SCT_MASK(SCT_TELETEXT)
#define ESF_MASK_SUBTIT \
(SCT_MASK(SCT_DVBSUB) | SCT_MASK(SCT_TEXTSUB))
#define ESF_MASK_CA \
SCT_MASK(SCT_CA)
#define ESF_MASK_OTHER \
SCT_MASK(SCT_MPEGTS)
extern uint32_t esfilterclsmask[];
TAILQ_HEAD(esfilter_entry_queue, esfilter);
extern struct esfilter_entry_queue esfilters[];
typedef enum {
ESFA_NONE = 0,
ESFA_USE, /* use this stream */
ESFA_ONCE, /* use this stream once per language */
ESFA_EXCLUSIVE, /* use this stream exclusively */
ESFA_EMPTY, /* use this stream when no streams were added */
ESFA_IGNORE,
ESFA_LAST = ESFA_IGNORE
} esfilter_action_t;
typedef struct esfilter {
idnode_t esf_id;
TAILQ_ENTRY(esfilter) esf_link;
int esf_class;
int esf_save;
int esf_index;
int esf_enabled;
uint32_t esf_type;
char esf_language[4];
char esf_service[UUID_HEX_SIZE];
int esf_pid;
uint16_t esf_caid;
uint32_t esf_caprovider;
int esf_action;
int esf_log;
char *esf_comment;
} esfilter_t;
esfilter_t *esfilter_create
(esfilter_class_t esf_class, const char *uuid, htsmsg_t *conf, int save);
const char * esfilter_class2txt(int cls);
const char * esfilter_action2txt(esfilter_action_t a);
void esfilter_init(void);
void esfilter_done(void);
#endif /* __TVH_ESFILTER_H__ */

View file

@ -62,6 +62,31 @@ static struct strtab HTTP_versiontab[] = {
static void http_parse_get_args(http_connection_t *hc, char *args);
/**
*
*/
const char *
http_cmd2str(int val)
{
return val2str(val, HTTP_cmdtab);
}
int http_str2cmd(const char *str)
{
return str2val(str, HTTP_cmdtab);
}
const char *
http_ver2str(int val)
{
return val2str(val, HTTP_versiontab);
}
int http_str2ver(const char *str)
{
return str2val(str, HTTP_versiontab);
}
/**
*
*/
@ -547,7 +572,6 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill)
/*
* Delete all arguments associated with a connection
*/
@ -582,7 +606,7 @@ http_arg_get(struct http_arg_list *list, const char *name)
* Set an argument associated with a connection
*/
void
http_arg_set(struct http_arg_list *list, char *key, char *val)
http_arg_set(struct http_arg_list *list, const char *key, const char *val)
{
http_arg_t *ra;
@ -814,8 +838,8 @@ http_serve(int fd, void **opaque, struct sockaddr_storage *peer,
memset(&hc, 0, sizeof(http_connection_t));
*opaque = &hc;
TAILQ_INIT(&hc.hc_args);
TAILQ_INIT(&hc.hc_req_args);
http_arg_init(&hc.hc_args);
http_arg_init(&hc.hc_req_args);
hc.hc_fd = fd;
hc.hc_peer = peer;

View file

@ -21,8 +21,9 @@
#include "htsbuf.h"
#include "url.h"
#include "tvhpoll.h"
TAILQ_HEAD(http_arg_list, http_arg);
typedef TAILQ_HEAD(http_arg_list, http_arg) http_arg_list_t;
typedef RB_HEAD(,http_arg) http_arg_tree_t;
@ -33,13 +34,77 @@ typedef struct http_arg {
char *val;
} http_arg_t;
#define HTTP_STATUS_OK 200
#define HTTP_STATUS_CONTINUE 100
#define HTTP_STATUS_PSWITCH 101
#define HTTP_STATUS_OK 200
#define HTTP_STATUS_CREATED 201
#define HTTP_STATUS_ACCEPTED 202
#define HTTP_STATUS_NON_AUTH_INFO 203
#define HTTP_STATUS_NO_CONTENT 204
#define HTTP_STATUS_RESET_CONTENT 205
#define HTTP_STATUS_PARTIAL_CONTENT 206
#define HTTP_STATUS_FOUND 302
#define HTTP_STATUS_BAD_REQUEST 400
#define HTTP_STATUS_UNAUTHORIZED 401
#define HTTP_STATUS_NOT_FOUND 404
#define HTTP_STATUS_MULTIPLE 300
#define HTTP_STATUS_MOVED 301
#define HTTP_STATUS_FOUND 302
#define HTTP_STATUS_SEE_OTHER 303
#define HTTP_STATUS_NOT_MODIFIED 304
#define HTTP_STATUS_USE_PROXY 305
#define HTTP_STATUS_TMP_REDIR 307
#define HTTP_STATUS_BAD_REQUEST 400
#define HTTP_STATUS_UNAUTHORIZED 401
#define HTTP_STATUS_PAYMENT 402
#define HTTP_STATUS_FORBIDDEN 403
#define HTTP_STATUS_NOT_FOUND 404
#define HTTP_STATUS_NOT_ALLOWED 405
#define HTTP_STATUS_NOT_ACCEPTABLE 406
#define HTTP_STATUS_PROXY_AUTH 407
#define HTTP_STATUS_TIMEOUT 408
#define HTTP_STATUS_CONFLICT 409
#define HTTP_STATUS_GONE 410
#define HTTP_STATUS_LENGTH 411
#define HTTP_STATUS_PRECONDITION 412
#define HTTP_STATUS_ENTITY_OVER 413
#define HTTP_STATUS_URI_TOO_LONG 414
#define HTTP_STATUS_UNSUPPORTED 415
#define HTTP_STATUS_BAD_RANGE 417
#define HTTP_STATUS_EXPECTATION 418
#define HTTP_STATUS_INTERNAL 500
#define HTTP_STATUS_NOT_IMPLEMENTED 501
#define HTTP_STATUS_BAD_GATEWAY 502
#define HTTP_STATUS_SERVICE 503
#define HTTP_STATUS_GATEWAY_TIMEOUT 504
#define HTTP_STATUS_HTTP_VERSION 505
typedef enum http_state {
HTTP_CON_WAIT_REQUEST,
HTTP_CON_READ_HEADER,
HTTP_CON_END,
HTTP_CON_POST_DATA,
HTTP_CON_SENDING,
HTTP_CON_SENT,
HTTP_CON_RECEIVING,
HTTP_CON_DONE,
HTTP_CON_IDLE,
HTTP_CON_OK
} http_state_t;
typedef enum http_cmd {
HTTP_CMD_GET,
HTTP_CMD_HEAD,
HTTP_CMD_POST,
RTSP_CMD_DESCRIBE,
RTSP_CMD_OPTIONS,
RTSP_CMD_SETUP,
RTSP_CMD_TEARDOWN,
RTSP_CMD_PLAY,
RTSP_CMD_PAUSE,
} http_cmd_t;
typedef enum http_ver {
HTTP_VERSION_1_0,
HTTP_VERSION_1_1,
RTSP_VERSION_1_0,
} http_ver_t;
typedef struct http_connection {
int hc_fd;
@ -51,36 +116,15 @@ typedef struct http_connection {
char *hc_url_orig;
int hc_keep_alive;
htsbuf_queue_t hc_reply;
htsbuf_queue_t hc_reply;
struct http_arg_list hc_args;
http_arg_list_t hc_args;
struct http_arg_list hc_req_args; /* Argumets from GET or POST request */
http_arg_list_t hc_req_args; /* Argumets from GET or POST request */
enum {
HTTP_CON_WAIT_REQUEST,
HTTP_CON_READ_HEADER,
HTTP_CON_END,
HTTP_CON_POST_DATA,
} hc_state;
enum {
HTTP_CMD_GET,
HTTP_CMD_HEAD,
HTTP_CMD_POST,
RTSP_CMD_DESCRIBE,
RTSP_CMD_OPTIONS,
RTSP_CMD_SETUP,
RTSP_CMD_TEARDOWN,
RTSP_CMD_PLAY,
RTSP_CMD_PAUSE,
} hc_cmd;
enum {
HTTP_VERSION_1_0,
HTTP_VERSION_1_1,
RTSP_VERSION_1_0,
} hc_version;
http_state_t hc_state;
http_cmd_t hc_cmd;
http_ver_t hc_version;
char *hc_username;
char *hc_password;
@ -99,11 +143,21 @@ typedef struct http_connection {
} http_connection_t;
const char *http_cmd2str(int val);
int http_str2cmd(const char *str);
const char *http_ver2str(int val);
int http_str2ver(const char *str);
static inline void http_arg_init(struct http_arg_list *list)
{
TAILQ_INIT(list);
}
void http_arg_flush(struct http_arg_list *list);
char *http_arg_get(struct http_arg_list *list, const char *name);
void http_arg_set(struct http_arg_list *list, char *key, char *val);
void http_arg_set(struct http_arg_list *list, const char *key, const char *val);
int http_tokenize(char *buf, char **vec, int vecsize, int delimiter);
@ -142,20 +196,144 @@ int http_access_verify(http_connection_t *hc, int mask);
void http_deescape(char *s);
/*
* HTTP/RTSP Client
*/
typedef struct http_client http_client_t;
typedef void (http_client_conn_cb) (void *p);
typedef size_t (http_client_data_cb) (void *p, void *buf, size_t len);
typedef void (http_client_fail_cb) (void *p);
typedef struct http_client_wcmd {
TAILQ_ENTRY(http_client_wcmd) link;
enum http_cmd wcmd;
int wcseq;
void *wbuf;
size_t wpos;
size_t wsize;
} http_client_wcmd_t;
struct http_client {
TAILQ_ENTRY(http_client) hc_link;
int hc_fd;
char *hc_scheme;
char *hc_host;
int hc_port;
tvhpoll_t *hc_efd;
int hc_pevents;
int hc_code;
http_ver_t hc_version;
http_cmd_t hc_cmd;
struct http_arg_list hc_args; /* header */
void *hc_aux;
size_t hc_data_limit;
size_t hc_io_size;
char *hc_data; /* data body */
size_t hc_data_size; /* data body size - result for caller */
time_t hc_ping_time; /* last issued command */
char *hc_rbuf; /* read buffer */
size_t hc_rsize; /* read buffer size */
size_t hc_rpos; /* read buffer position */
size_t hc_hsize; /* header size in bytes */
size_t hc_csize; /* contents size in bytes */
char *hc_chunk;
size_t hc_chunk_size;
size_t hc_chunk_csize;
size_t hc_chunk_alloc;
size_t hc_chunk_pos;
char *hc_location;
int hc_redirects;
int hc_result;
int hc_shutdown:1;
int hc_sending:1;
int hc_reconnected:1;
int hc_keepalive:1;
int hc_in_data:1;
int hc_chunked:1;
int hc_chunk_trails:1;
int hc_handle_location:1; /* handle the redirection (location) requests */
http_client_wcmd_t *hc_wcmd;
TAILQ_HEAD(,http_client_wcmd) hc_wqueue;
int hc_verify_peer; /* SSL - verify peer */
int hc_cseq; /* RTSP */
int hc_rcseq; /* RTSP - expected cseq */
char *hc_rtsp_session;
char *hc_rtp_dest;
int hc_rtp_port;
int hc_rtpc_port;
int hc_rtp_multicast:1;
long hc_rtsp_stream_id;
int hc_rtp_timeout;
struct http_client_ssl *hc_ssl; /* ssl internals */
/* callbacks */
int (*hc_hdr_received) (http_client_t *hc);
int (*hc_data_received)(http_client_t *hc, void *buf, size_t len);
int (*hc_data_complete)(http_client_t *hc);
void (*hc_conn_closed) (http_client_t *hc, int err);
};
void http_client_init ( void );
void http_client_done ( void );
http_client_t*
http_connect ( const url_t *url,
http_client_conn_cb conn_cb,
http_client_data_cb data_cb,
http_client_fail_cb fail_cb,
void *p );
void http_close ( http_client_t *hc );
http_client_connect ( void *aux, http_ver_t ver,
const char *scheme, const char *host, int port );
void http_client_register ( http_client_t *hc );
void http_client_close ( http_client_t *hc );
int http_client_send( http_client_t *hc, http_cmd_t cmd,
const char *path, const char *query,
http_arg_list_t *header, void *body, size_t body_size );
int http_client_simple( http_client_t *hc, const url_t *url);
int http_client_clear_state( http_client_t *hc );
int http_client_run( http_client_t *hc );
void http_client_ssl_peer_verify( http_client_t *hc, int verify );
/*
* RTSP helpers
*/
int rtsp_send( http_client_t *hc, http_cmd_t cmd, const char *path,
const char *query, http_arg_list_t *hdr );
void rtsp_clear_session( http_client_t *hc );
int rtsp_options_decode( http_client_t *hc );
static inline int rtsp_options( http_client_t *hc ) {
return rtsp_send(hc, RTSP_CMD_OPTIONS, NULL, NULL, NULL);
}
int rtsp_setup_decode( http_client_t *hc, int satip );
int rtsp_setup( http_client_t *hc, const char *path, const char *query,
const char *multicast_addr, int rtp_port, int rtpc_port );
static inline int
rtsp_play( http_client_t *hc, const char *path, const char *query ) {
return rtsp_send(hc, RTSP_CMD_PLAY, path, query, NULL);
}
static inline int
rtsp_teardown( http_client_t *hc, const char *path, const char *query ) {
return rtsp_send(hc, RTSP_CMD_TEARDOWN, path, query, NULL);
}
int rtsp_describe_decode( http_client_t *hc );
static inline int
rtsp_describe( http_client_t *hc, const char *path, const char *query ) {
return rtsp_send(hc, RTSP_CMD_DESCRIBE, path, query, NULL);
}
#endif /* HTTP_H_ */

View file

@ -1,265 +0,0 @@
/*
* Tvheadend - HTTP client functions
*
* Copyright (C) 2013 Adam Sutton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tvheadend.h"
#include "tvhpoll.h"
#include "redblack.h"
#include "queue.h"
#include "url.h"
#include "http.h"
#if ENABLE_CURL
#include <curl/curl.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
/*
* Client definition
*/
struct http_client
{
CURL *hc_curl;
int hc_fd;
url_t hc_url;
int hc_begin;
/* Callbacks */
http_client_conn_cb *hc_conn;
http_client_data_cb *hc_data;
http_client_fail_cb *hc_fail;
void *hc_opaque;
TAILQ_ENTRY(http_client) hc_link;
};
/*
* Global state
*/
static tvhpoll_t *http_poll;
static TAILQ_HEAD(,http_client) http_clients;
static pthread_mutex_t http_lock;
static CURLM *http_curlm;
/*
* Disable
*/
static void
http_remove ( http_client_t *hc )
{
tvhpoll_event_t ev;
ev.fd = hc->hc_fd;
/* Remove */
curl_multi_remove_handle(http_curlm, hc->hc_curl);
tvhpoll_rem(http_poll, &ev, 1);
TAILQ_REMOVE(&http_clients, hc, hc_link);
/* Free CURL memory */
curl_easy_cleanup(hc->hc_curl);
hc->hc_curl = NULL;
}
/*
* New socket
*/
static int
http_curl_socket ( CURL *c, int fd, int a, void *u, void *s )
{
http_client_t *hc;
tvhpoll_event_t ev = { 0 };
ev.fd = fd;
/* Find client */
TAILQ_FOREACH(hc, &http_clients, hc_link)
if (hc->hc_curl == c)
break;
/* Invalid */
if (!hc)
goto done;
/* Remove */
if (a == CURL_POLL_REMOVE) {
//http_remove(hc);
/* Set */
} else if (a & CURL_POLL_INOUT) {
if (a & CURL_POLL_IN)
ev.events |= TVHPOLL_IN;
if (a & CURL_POLL_OUT)
ev.events |= TVHPOLL_OUT;
ev.data.fd = fd;
hc->hc_fd = fd;
tvhpoll_add(http_poll, &ev, 1);
}
/* Done */
done:
return 0;
}
/*
* Data
*/
static size_t
http_curl_data ( void *buf, size_t len, size_t n, void *p )
{
http_client_t *hc = p;
if (!hc->hc_begin && hc->hc_conn)
hc->hc_conn(hc->hc_opaque);
hc->hc_begin = 1;
len = hc->hc_data(hc->hc_opaque, buf, len * n);
return len;
}
/*
* Data thread
*/
static void *
http_thread ( void *p )
{
int n, e, run = 0;
tvhpoll_event_t ev;
http_client_t *hc;
while (tvheadend_running) {
n = tvhpoll_wait(http_poll, &ev, 1, -1);
if (n < 0) {
if (tvheadend_running)
tvherror("http_client", "tvhpoll_wait() error");
break;
} else {
pthread_mutex_lock(&http_lock);
TAILQ_FOREACH(hc, &http_clients, hc_link)
if (hc->hc_fd == ev.data.fd)
break;
if (hc && (ev.events & (TVHPOLL_IN | TVHPOLL_OUT))) {
e = 0;
if (ev.events & TVHPOLL_IN) e |= CURL_POLL_IN;
if (ev.events & TVHPOLL_OUT) e |= CURL_POLL_OUT;
curl_multi_socket_action(http_curlm, ev.data.fd, 0, &run);
}
pthread_mutex_unlock(&http_lock);
}
}
return NULL;
}
/*
* Setup a connection (async)
*/
http_client_t *
http_connect
( const url_t *url,
http_client_conn_cb conn_cb,
http_client_data_cb data_cb,
http_client_fail_cb fail_cb,
void *p )
{
int run;
/* Setup structure */
http_client_t *hc = calloc(1, sizeof(http_client_t));
hc->hc_curl = curl_easy_init();
hc->hc_url = *url;
hc->hc_conn = conn_cb;
hc->hc_data = data_cb;
hc->hc_fail = fail_cb;
hc->hc_opaque = p;
/* Store */
pthread_mutex_lock(&http_lock);
TAILQ_INSERT_TAIL(&http_clients, hc, hc_link);
/* Setup connection */
curl_easy_setopt(hc->hc_curl, CURLOPT_URL, url->raw);
curl_easy_setopt(hc->hc_curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEFUNCTION, http_curl_data);
curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEDATA, hc);
curl_multi_add_handle(http_curlm, hc->hc_curl);
curl_multi_socket_action(http_curlm, CURL_SOCKET_TIMEOUT, 0, &run);
pthread_mutex_unlock(&http_lock);
return hc;
}
/*
* Cancel
*/
void
http_close ( http_client_t *hc )
{
pthread_mutex_lock(&http_lock);
http_remove(hc);
free(hc);
pthread_mutex_unlock(&http_lock);
}
/*
* Initialise subsystem
*/
pthread_t http_client_tid;
void
http_client_init ( void )
{
/* Setup list */
pthread_mutex_init(&http_lock, NULL);
TAILQ_INIT(&http_clients);
/* Initialise curl */
curl_global_init(CURL_GLOBAL_ALL);
http_curlm = curl_multi_init();
curl_multi_setopt(http_curlm, CURLMOPT_SOCKETFUNCTION, http_curl_socket);
/* Setup poll */
http_poll = tvhpoll_create(10);
/* Setup thread */
tvhthread_create(&http_client_tid, NULL, http_thread, NULL, 0);
}
void
http_client_done ( void )
{
pthread_kill(http_client_tid, SIGTERM);
pthread_join(http_client_tid, NULL);
tvhpoll_destroy(http_poll);
curl_multi_cleanup(http_curlm);
curl_global_cleanup();
}
#else /* ENABLE_CURL */
void
http_client_init ( void )
{
}
void
http_client_done ( void )
{
}
#endif /* ENABLE_CURL */

1715
src/httpc.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -140,20 +140,40 @@ idnode_unlink(idnode_t *in)
/**
*
*/
void
idnode_delete(idnode_t *in)
static void
idnode_handler(size_t off, idnode_t *in)
{
void (**fcn)(idnode_t *);
lock_assert(&global_lock);
const idclass_t *idc = in->in_class;
while (idc) {
if (idc->ic_delete) {
idc->ic_delete(in);
fcn = (void *)idc + off;
if (*fcn) {
(*fcn)(in);
break;
}
idc = idc->ic_super;
}
}
void
idnode_delete(idnode_t *in)
{
return idnode_handler(offsetof(idclass_t, ic_delete), in);
}
void
idnode_moveup(idnode_t *in)
{
return idnode_handler(offsetof(idclass_t, ic_moveup), in);
}
void
idnode_movedown(idnode_t *in)
{
return idnode_handler(offsetof(idclass_t, ic_movedown), in);
}
/* **************************************************************************
* Info
* *************************************************************************/
@ -587,6 +607,7 @@ idnode_filter_clear
else
free(ele->u.s);
}
free(ele->key);
free(ele);
}
}

View file

@ -42,7 +42,8 @@ typedef struct idnode_set
/*
* Class definition
*/
typedef struct idclass {
typedef struct idclass idclass_t;
struct idclass {
const struct idclass *ic_super; /// Parent class
const char *ic_class; /// Class name
const char *ic_caption; /// Class description
@ -51,11 +52,13 @@ typedef struct idclass {
const char *ic_event; /// Events to fire on add/delete/title
/* Callbacks */
idnode_set_t *(*ic_get_childs)(idnode_t *self);
const char *(*ic_get_title) (idnode_t *self);
void (*ic_save) (idnode_t *self);
void (*ic_delete) (idnode_t *self);
} idclass_t;
idnode_set_t *(*ic_get_childs) (idnode_t *self);
const char *(*ic_get_title) (idnode_t *self);
void (*ic_save) (idnode_t *self);
void (*ic_delete) (idnode_t *self);
void (*ic_moveup) (idnode_t *self);
void (*ic_movedown) (idnode_t *self);
};
/*
* Node definition
@ -120,6 +123,8 @@ const char *idnode_get_title (idnode_t *in);
int idnode_is_leaf (idnode_t *in);
int idnode_is_instance (idnode_t *in, const idclass_t *idc);
void idnode_delete (idnode_t *in);
void idnode_moveup (idnode_t *in);
void idnode_movedown (idnode_t *in);
void *idnode_find (const char *uuid, const idclass_t *idc);
idnode_set_t *idnode_find_all(const idclass_t *idc);

View file

@ -35,12 +35,7 @@
#include "redblack.h"
#include "notify.h"
#include "prop.h"
#if ENABLE_IMAGECACHE
#define CURL_STATICLIB
#include <curl/curl.h>
#include <curl/easy.h>
#endif
#include "http.h"
/*
* Image metadata
@ -139,10 +134,15 @@ imagecache_image_add ( imagecache_image_t *img )
static int
imagecache_image_fetch ( imagecache_image_t *img )
{
int res = 1;
CURL *curl;
int res = 1, r;
FILE *fp;
url_t url;
char tmp[256], path[256];
tvhpoll_event_t ev;
tvhpoll_t *efd = NULL;
http_client_t *hc;
memset(&url, 0, sizeof(url));
/* Open file */
if (hts_settings_buildpath(path, sizeof(path), "imagecache/data/%d",
@ -156,25 +156,54 @@ imagecache_image_fetch ( imagecache_image_t *img )
/* Build command */
tvhlog(LOG_DEBUG, "imagecache", "fetch %s", img->url);
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, img->url);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "TVHeadend");
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
if (imagecache_conf.ignore_sslcert)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
memset(&url, 0, sizeof(url));
if (urlparse(img->url, &url)) {
tvherror("imagecache", "Unable to parse url '%s'", img->url);
goto error;
}
/* Fetch (release lock, incase of delays) */
pthread_mutex_unlock(&global_lock);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
fclose(fp);
hc = http_client_connect(NULL, HTTP_VERSION_1_1, url.scheme,
url.host, url.port);
if (hc == NULL)
goto error;
http_client_ssl_peer_verify(hc, imagecache_conf.ignore_sslcert ? 0 : 1);
hc->hc_handle_location = 1;
hc->hc_data_limit = 256*1024;
efd = tvhpoll_create(1);
hc->hc_efd = efd;
r = http_client_simple(hc, &url);
if (r < 0)
goto error;
while (tvheadend_running) {
r = tvhpoll_wait(efd, &ev, 1, -1);
if (r < 0)
break;
if (r == 0)
continue;
r = http_client_run(hc);
if (r < 0)
break;
if (r == HTTP_CON_DONE) {
if (hc->hc_code == HTTP_STATUS_OK && hc->hc_data_size > 0) {
fwrite(hc->hc_data, hc->hc_data_size, 1, fp);
res = 0;
}
break;
}
}
pthread_mutex_lock(&global_lock);
/* Process */
error:
urlreset(&url);
tvhpoll_destroy(efd);
img->state = IDLE;
time(&img->updated); // even if failed (possibly request sooner?)
if (res) {

View file

@ -128,6 +128,9 @@ void tvh_input_stream_destroy ( tvh_input_stream_t *st );
#if ENABLE_LINUXDVB
#include "input/mpegts/linuxdvb.h"
#endif
#if ENABLE_SATIP_CLIENT
#include "input/mpegts/satip/satip.h"
#endif
#endif
#endif /* __TVH_INPUT_H__ */

View file

@ -19,7 +19,8 @@
#include "input.h"
void
mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners )
mpegts_init ( int linuxdvb_mask, str_list_t *satip_client,
str_list_t *tsfiles, int tstuners )
{
/* Register classes (avoid API 400 errors due to not yet defined) */
idclass_register(&mpegts_network_class);
@ -51,6 +52,11 @@ mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners )
linuxdvb_init(linuxdvb_mask);
#endif
/* SAT>IP DVB client */
#if ENABLE_SATIP_CLIENT
satip_init(satip_client);
#endif
/* Mux schedulers */
#if ENABLE_MPEGTS
mpegts_mux_sched_init();
@ -71,6 +77,9 @@ mpegts_done ( void )
#if ENABLE_LINUXDVB
tvhftrace("main", linuxdvb_done);
#endif
#if ENABLE_SATIP_CLIENT
tvhftrace("main", satip_done);
#endif
#if ENABLE_TSFILE
tvhftrace("main", tsfile_done);
#endif

View file

@ -68,7 +68,8 @@ extern const idclass_t mpegts_input_class;
* Setup / Tear down
* *************************************************************************/
void mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners );
void mpegts_init ( int linuxdvb_mask, str_list_t *satip_client,
str_list_t *tsfiles, int tstuners );
void mpegts_done ( void );
/* **************************************************************************
@ -505,7 +506,7 @@ struct mpegts_input
/*
* Functions
*/
int (*mi_is_enabled) (mpegts_input_t*);
int (*mi_is_enabled) (mpegts_input_t*, mpegts_mux_t *mm);
void (*mi_enabled_updated)(mpegts_input_t*);
void (*mi_display_name) (mpegts_input_t*, char *buf, size_t len);
int (*mi_is_free) (mpegts_input_t*);

View file

@ -218,6 +218,7 @@ iptv_input_start_mux ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi )
im->mm_active = NULL;
pthread_mutex_unlock(&iptv_lock);
urlreset(&url);
return ret;
}
@ -239,7 +240,8 @@ iptv_input_stop_mux ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi )
/* Close file */
if (im->mm_iptv_fd > 0) {
close(im->mm_iptv_fd); // removes from poll
udp_close(im->mm_iptv_connection); // removes from poll
im->mm_iptv_connection = NULL;
im->mm_iptv_fd = -1;
}

View file

@ -21,27 +21,29 @@
#include "iptv_private.h"
#include "http.h"
#if ENABLE_CURL
/*
* Connected
*/
static void
iptv_http_conn ( void *p )
static int
iptv_http_header ( http_client_t *hc )
{
pthread_mutex_lock(&global_lock);
iptv_input_mux_started(p);
pthread_mutex_unlock(&global_lock);
/* multiple headers for redirections */
if (hc->hc_code == HTTP_STATUS_OK) {
pthread_mutex_lock(&global_lock);
iptv_input_mux_started(hc->hc_aux);
pthread_mutex_unlock(&global_lock);
}
return 0;
}
/*
* Receive data
*/
static size_t
static int
iptv_http_data
( void *p, void *buf, size_t len )
( http_client_t *hc, void *buf, size_t len )
{
iptv_mux_t *im = p;
iptv_mux_t *im = hc->hc_aux;
pthread_mutex_lock(&iptv_lock);
@ -52,7 +54,7 @@ iptv_http_data
pthread_mutex_unlock(&iptv_lock);
return len;
return 0;
}
/*
@ -63,8 +65,21 @@ iptv_http_start
( iptv_mux_t *im, const url_t *u )
{
http_client_t *hc;
if (!(hc = http_connect(u, iptv_http_conn, iptv_http_data, NULL, im)))
int r;
if (!(hc = http_client_connect(im, HTTP_VERSION_1_1, u->scheme,
u->host, u->port)))
return SM_CODE_TUNING_FAILED;
hc->hc_hdr_received = iptv_http_header;
hc->hc_data_received = iptv_http_data;
hc->hc_handle_location = 1; /* allow redirects */
hc->hc_chunk_size = 128*1024; /* increase buffering */
http_client_register(hc); /* register to the HTTP thread */
r = http_client_simple(hc, u);
if (r < 0) {
http_client_close(hc);
return SM_CODE_TUNING_FAILED;
}
im->im_data = hc;
return 0;
@ -77,7 +92,7 @@ static void
iptv_http_stop
( iptv_mux_t *im )
{
http_close(im->im_data);
http_client_close(im->im_data);
}
@ -102,12 +117,3 @@ iptv_http_init ( void )
};
iptv_handler_register(ih, 2);
}
#else /* ENABLE_CURL */
void
iptv_http_init ( void )
{
}
#endif /* ENABLE_CURL */

View file

@ -23,6 +23,7 @@
#include "input.h"
#include "htsbuf.h"
#include "url.h"
#include "udp.h"
#define IPTV_PKT_SIZE (300*188)
@ -72,6 +73,7 @@ struct iptv_mux
mpegts_mux_t;
int mm_iptv_fd;
udp_connection_t *mm_iptv_connection;
char *mm_iptv_url;
char *mm_iptv_interface;

View file

@ -45,137 +45,23 @@
static int
iptv_udp_start ( iptv_mux_t *im, const url_t *url )
{
int fd, solip, rxsize, reuse = 1, ipv6 = 0;
struct ifreq ifr;
struct in_addr saddr;
struct in6_addr s6addr;
char name[256], buf[256];
char name[256];
udp_connection_t *conn;
im->mm_display_name((mpegts_mux_t*)im, name, sizeof(name));
/* Determine if this is IPv6 */
if (!inet_pton(AF_INET, url->host, &saddr)) {
ipv6 = 1;
if (!inet_pton(AF_INET6, url->host, &s6addr)) {
tvherror("iptv", "%s - failed to process host", name);
return SM_CODE_TUNING_FAILED;
}
}
/* Open socket */
if ((fd = tvh_socket(ipv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0)) == -1) {
tvherror("iptv", "%s - failed to create socket [%s]",
name, strerror(errno));
conn = udp_bind("iptv", name, url->host, url->port,
im->mm_iptv_interface, IPTV_PKT_SIZE);
if (conn == UDP_FATAL_ERROR)
return SM_CODE_TUNING_FAILED;
}
/* Mark reuse address */
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
/* Bind to interface */
memset(&ifr, 0, sizeof(ifr));
if (im->mm_iptv_interface && *im->mm_iptv_interface) {
snprintf(ifr.ifr_name, IFNAMSIZ, "%s", im->mm_iptv_interface);
if (ioctl(fd, SIOCGIFINDEX, &ifr)) {
tvherror("iptv", "%s - could not find interface %s",
name, im->mm_iptv_interface);
goto error;
}
}
/* IPv4 */
if (!ipv6) {
struct ip_mreqn m;
struct sockaddr_in sin;
memset(&m, 0, sizeof(m));
memset(&sin, 0, sizeof(sin));
/* Bind */
sin.sin_family = AF_INET;
sin.sin_port = htons(url->port);
sin.sin_addr = saddr;
if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
inet_ntop(AF_INET, &sin.sin_addr, buf, sizeof(buf));
tvherror("iptv", "%s - cannot bind %s:%hd [e=%s]",
name, buf, ntohs(sin.sin_port), strerror(errno));
goto error;
}
/* Join group */
m.imr_multiaddr = sin.sin_addr;
m.imr_address.s_addr = 0;
#if defined(PLATFORM_LINUX)
m.imr_ifindex = ifr.ifr_ifindex;
#elif defined(PLATFORM_FREEBSD)
m.imr_ifindex = ifr.ifr_index;
#endif
#ifdef SOL_IP
solip = SOL_IP;
#else
{
struct protoent *pent;
pent = getprotobyname("ip");
solip = (pent != NULL) ? pent->p_proto : 0;
}
#endif
if (setsockopt(fd, solip, IP_ADD_MEMBERSHIP, &m, sizeof(m))) {
inet_ntop(AF_INET, &m.imr_multiaddr, buf, sizeof(buf));
tvhwarn("iptv", "%s - cannot join %s [%s]",
name, buf, strerror(errno));
}
/* Bind to IPv6 group */
} else {
struct ipv6_mreq m;
struct sockaddr_in6 sin;
memset(&m, 0, sizeof(m));
memset(&sin, 0, sizeof(sin));
/* Bind */
sin.sin6_family = AF_INET6;
sin.sin6_port = htons(url->port);
sin.sin6_addr = s6addr;
if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
inet_ntop(AF_INET6, &sin.sin6_addr, buf, sizeof(buf));
tvherror("iptv", "%s - cannot bind %s:%hd [e=%s]",
name, buf, ntohs(sin.sin6_port), strerror(errno));
goto error;
}
/* Join group */
m.ipv6mr_multiaddr = sin.sin6_addr;
#if defined(PLATFORM_LINUX)
m.ipv6mr_interface = ifr.ifr_ifindex;
#elif defined(PLATFORM_FREEBSD)
m.ipv6mr_interface = ifr.ifr_index;
#endif
#ifdef SOL_IPV6
if (setsockopt(fd, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &m, sizeof(m))) {
inet_ntop(AF_INET, &m.ipv6mr_multiaddr, buf, sizeof(buf));
tvhwarn("iptv", "%s - cannot join %s [%s]",
name, buf, strerror(errno));
}
#else
tvherror("iptv", "IPv6 multicast not supported");
goto error;
#endif
}
/* Increase RX buffer size */
rxsize = IPTV_PKT_SIZE;
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rxsize, sizeof(rxsize)) == -1)
tvhwarn("iptv", "%s - cannot increase UDP rx buffer size [%s]",
name, strerror(errno));
if (conn == NULL)
return -1;
/* Done */
im->mm_iptv_fd = fd;
im->mm_iptv_fd = conn->fd;
im->mm_iptv_connection = conn;
iptv_input_mux_started(im);
return 0;
error:
close(fd);
return -1;
}
static ssize_t

View file

@ -119,7 +119,7 @@ linuxdvb_adapter_is_enabled ( linuxdvb_adapter_t *la )
{
linuxdvb_frontend_t *lfe;
LIST_FOREACH(lfe, &la->la_frontends, lfe_link) {
if (lfe->mi_is_enabled((mpegts_input_t*)lfe))
if (lfe->mi_is_enabled((mpegts_input_t*)lfe, NULL))
return 1;
}
return 0;

View file

@ -252,7 +252,7 @@ linuxdvb_frontend_get_grace ( mpegts_input_t *mi, mpegts_mux_t *mm )
}
static int
linuxdvb_frontend_is_enabled ( mpegts_input_t *mi )
linuxdvb_frontend_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm )
{
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
if (lfe->lfe_fe_path == NULL) return 0;

View file

@ -162,7 +162,7 @@ const idclass_t mpegts_input_class =
* *************************************************************************/
static int
mpegts_input_is_enabled ( mpegts_input_t *mi )
mpegts_input_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm )
{
return mi->mi_enabled;
}

View file

@ -440,7 +440,7 @@ mpegts_mux_start
/* First pass - free only */
if (!pass) {
int e = mmi->mmi_input->mi_is_enabled(mmi->mmi_input);
int e = mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mm);
int f = mmi->mmi_input->mi_is_free(mmi->mmi_input);
tvhtrace("mpegts", "%s - enabled %d free %d", buf, e, f);
if (e) enabled = 1;
@ -457,7 +457,7 @@ mpegts_mux_start
} else {
/* Enabled, valid and lower weight */
if (mmi->mmi_input->mi_is_enabled(mmi->mmi_input) &&
if (mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mm) &&
!mmi->mmi_tune_failed &&
(weight > mmi->mmi_input->mi_get_weight(mmi->mmi_input))) {
tune = mmi;

View file

@ -592,7 +592,7 @@ dvb_mux_create_instances ( mpegts_mux_t *mm )
mpegts_network_link_t *mnl;
LIST_FOREACH(mnl, &mm->mm_network->mn_inputs, mnl_mn_link) {
mpegts_input_t *mi = mnl->mnl_input;
if (mi->mi_is_enabled(mi))
if (mi->mi_is_enabled(mi, mm))
mi->mi_create_mux_instance(mi, mm);
}
}

View file

@ -189,7 +189,7 @@ mpegts_service_enlist(service_t *t, struct service_instance_list *sil)
if (mmi->mmi_tune_failed)
continue;
if (!mmi->mmi_input->mi_is_enabled(mmi->mmi_input)) continue;
if (!mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mmi->mmi_mux)) continue;
/* Set weight to -1 (forced) for already active mux */
if (mmi->mmi_mux->mm_active == mmi) {

View file

@ -0,0 +1,921 @@
/*
* Tvheadend - SAT-IP client
*
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tvheadend.h"
#include "input.h"
#include "htsbuf.h"
#include "htsmsg_xml.h"
#include "upnp.h"
#include "settings.h"
#include "satip_private.h"
#include <arpa/inet.h>
#include <openssl/sha.h>
static void satip_device_discovery_start( void );
/*
* SAT-IP client
*/
static void
satip_device_class_save ( idnode_t *in )
{
satip_device_save((satip_device_t *)in);
}
static idnode_set_t *
satip_device_class_get_childs ( idnode_t *in )
{
satip_device_t *sd = (satip_device_t *)in;
idnode_set_t *is = idnode_set_create();
satip_frontend_t *lfe;
TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link)
idnode_set_add(is, &lfe->ti_id, NULL);
return is;
}
static const char *
satip_device_class_get_title( idnode_t *in )
{
static char buf[256];
satip_device_t *sd = (satip_device_t *)in;
snprintf(buf, sizeof(buf),
"%s - %s", sd->sd_info.friendlyname, sd->sd_info.addr);
return buf;
}
const idclass_t satip_device_class =
{
.ic_class = "satip_client",
.ic_caption = "SAT>IP Client",
.ic_save = satip_device_class_save,
.ic_get_childs = satip_device_class_get_childs,
.ic_get_title = satip_device_class_get_title,
.ic_properties = (const property_t[]){
{
.type = PT_BOOL,
.id = "fullmux_ok",
.name = "Full Mux Rx mode supported",
.opts = PO_ADVANCED,
.off = offsetof(satip_device_t, sd_fullmux_ok),
},
{
.type = PT_INT,
.id = "sigscale",
.name = "Signal scale (240 or 100)",
.opts = PO_ADVANCED,
.off = offsetof(satip_device_t, sd_sig_scale),
},
{
.type = PT_INT,
.id = "pids_max",
.name = "Maximum PIDs",
.opts = PO_ADVANCED,
.off = offsetof(satip_device_t, sd_pids_max),
},
{
.type = PT_INT,
.id = "pids_len",
.name = "Maximum length of PIDs",
.opts = PO_ADVANCED,
.off = offsetof(satip_device_t, sd_pids_len),
},
{
.type = PT_BOOL,
.id = "pids_deladd",
.name = "addpids/delpids supported",
.opts = PO_ADVANCED,
.off = offsetof(satip_device_t, sd_pids_deladd),
},
{
.type = PT_BOOL,
.id = "pids0",
.name = "PIDs in setup",
.opts = PO_ADVANCED,
.off = offsetof(satip_device_t, sd_pids0),
},
{
.type = PT_STR,
.id = "addr",
.name = "IP Address",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.addr),
},
{
.type = PT_STR,
.id = "device_uuid",
.name = "UUID",
.opts = PO_RDONLY,
.off = offsetof(satip_device_t, sd_info.uuid),
},
{
.type = PT_STR,
.id = "friendly",
.name = "Friendly Name",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.friendlyname),
},
{
.type = PT_STR,
.id = "serialnum",
.name = "Serial Number",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.serialnum),
},
{
.type = PT_STR,
.id = "tunercfg",
.name = "Tuner Configuration",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.tunercfg),
},
{
.type = PT_STR,
.id = "manufacturer",
.name = "Manufacturer",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.manufacturer),
},
{
.type = PT_STR,
.id = "manufurl",
.name = "Manufacturer URL",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.manufacturerURL),
},
{
.type = PT_STR,
.id = "modeldesc",
.name = "Model Description",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.modeldesc),
},
{
.type = PT_STR,
.id = "modelname",
.name = "Model Name",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.modelname),
},
{
.type = PT_STR,
.id = "modelnum",
.name = "Model Number",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.modelnum),
},
{
.type = PT_STR,
.id = "bootid",
.name = "Boot ID",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.bootid),
},
{
.type = PT_STR,
.id = "configid",
.name = "Config ID",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.configid),
},
{
.type = PT_STR,
.id = "deviceid",
.name = "Device ID",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.deviceid),
},
{
.type = PT_STR,
.id = "presentation",
.name = "Presentation",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.presentation),
},
{
.type = PT_STR,
.id = "location",
.name = "Location",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.location),
},
{
.type = PT_STR,
.id = "server",
.name = "Server",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.server),
},
{
.type = PT_STR,
.id = "myaddr",
.name = "Local IP Address",
.opts = PO_RDONLY | PO_NOSAVE,
.off = offsetof(satip_device_t, sd_info.myaddr),
},
{}
}
};
/*
* Create entry
*/
static void
satip_device_calc_bin_uuid( uint8_t *uuid, const char *satip_uuid )
{
SHA_CTX sha1;
SHA1_Init(&sha1);
SHA1_Update(&sha1, (void*)satip_uuid, strlen(satip_uuid));
SHA1_Final(uuid, &sha1);
}
static void
satip_device_calc_uuid( uuid_t *uuid, const char *satip_uuid )
{
uint8_t uuidbin[20];
satip_device_calc_bin_uuid(uuidbin, satip_uuid);
bin2hex(uuid->hex, sizeof(uuid->hex), uuidbin, sizeof(uuidbin));
}
static void
satip_device_hack( satip_device_t *sd )
{
if (sd->sd_info.deviceid[0] &&
strcmp(sd->sd_info.server, "Linux/1.0 UPnP/1.1 IDL4K/1.0") == 0) {
/* AXE Linux distribution - Inverto firmware */
/* version V1.13.0.105 and probably less */
/* really ugly firmware - soooooo much restrictions */
sd->sd_fullmux_ok = 0;
sd->sd_pids_max = 32;
sd->sd_pids_deladd = 0;
tvhwarn("satip", "Detected old Inverto firmware V1.13.0.105 and less");
tvhwarn("satip", "Upgrade to V1.16.0.120 - http://http://www.inverto.tv/support/ - IDL400s");
} else if (strstr(sd->sd_info.location, ":8888/octonet.xml")) {
/* OctopusNet requires pids in the SETUP RTSP command */
sd->sd_pids0 = 1;
}
}
static satip_device_t *
satip_device_create( satip_device_info_t *info )
{
satip_device_t *sd = calloc(1, sizeof(satip_device_t));
uuid_t uuid;
htsmsg_t *conf = NULL, *feconf = NULL;
char *argv[10];
int i, j, n, m, fenum, t2, save = 0;
dvb_fe_type_t type;
satip_device_calc_uuid(&uuid, info->uuid);
conf = hts_settings_load("input/satip/adapters/%s", uuid.hex);
/* some sane defaults */
sd->sd_fullmux_ok = 1;
sd->sd_pids_len = 127;
sd->sd_pids_max = 32;
sd->sd_pids_deladd = 1;
sd->sd_sig_scale = 240;
if (!tvh_hardware_create0((tvh_hardware_t*)sd, &satip_device_class,
uuid.hex, conf)) {
free(sd);
return NULL;
}
TAILQ_INIT(&sd->sd_frontends);
/* we may check if uuid matches, but the SHA hash should be enough */
if (sd->sd_info.uuid)
free(sd->sd_info.uuid);
#define ASSIGN(x) sd->sd_info.x = info->x; info->x = NULL
ASSIGN(myaddr);
ASSIGN(addr);
ASSIGN(uuid);
ASSIGN(bootid);
ASSIGN(configid);
ASSIGN(deviceid);
ASSIGN(server);
ASSIGN(location);
ASSIGN(friendlyname);
ASSIGN(manufacturer);
ASSIGN(manufacturerURL);
ASSIGN(modeldesc);
ASSIGN(modelname);
ASSIGN(modelnum);
ASSIGN(serialnum);
ASSIGN(presentation);
ASSIGN(tunercfg);
#undef ASSIGN
/*
* device specific hacks
*/
satip_device_hack(sd);
if (conf)
feconf = htsmsg_get_map(conf, "frontends");
save = !conf || !feconf;
n = http_tokenize(sd->sd_info.tunercfg, argv, 10, ',');
for (i = 0, fenum = 1; i < n; i++) {
type = DVB_TYPE_NONE;
t2 = 0;
if (strncmp(argv[i], "DVBS2-", 6) == 0) {
type = DVB_TYPE_S;
m = atoi(argv[i] + 6);
} else if (strncmp(argv[i], "DVBT2-", 6) == 0) {
type = DVB_TYPE_T;
m = atoi(argv[i] + 6);
t2 = 1;
} else if (strncmp(argv[i], "DVBT-", 5) == 0) {
type = DVB_TYPE_T;
m = atoi(argv[i] + 5);
} else if (strncmp(argv[i], "DVBC-", 5) == 0) {
type = DVB_TYPE_C;
m = atoi(argv[i] + 5);
}
if (type == DVB_TYPE_NONE) {
tvhlog(LOG_ERR, "satip", "%s: bad tuner type [%s]", sd->sd_info.addr, argv[i]);
} else if (m < 0 || m > 32) {
tvhlog(LOG_ERR, "satip", "%s: bad tuner count [%s]", sd->sd_info.addr, argv[i]);
} else {
for (j = 0; j < m; j++)
if (satip_frontend_create(feconf, sd, type, t2, fenum))
fenum++;
}
}
if (save)
satip_device_save(sd);
htsmsg_destroy(conf);
return sd;
}
static satip_device_t *
satip_device_find( const char *satip_uuid )
{
tvh_hardware_t *th;
uint8_t binuuid[20];
satip_device_calc_bin_uuid(binuuid, satip_uuid);
TVH_HARDWARE_FOREACH(th) {
if (idnode_is_instance(&th->th_id, &satip_device_class) &&
memcmp(th->th_id.in_uuid, binuuid, UUID_BIN_SIZE) == 0)
return (satip_device_t *)th;
}
return NULL;
}
static satip_device_t *
satip_device_find_by_descurl( const char *descurl )
{
tvh_hardware_t *th;
TVH_HARDWARE_FOREACH(th) {
if (idnode_is_instance(&th->th_id, &satip_device_class) &&
strcmp(((satip_device_t *)th)->sd_info.location, descurl) == 0)
return (satip_device_t *)th;
}
return NULL;
}
void
satip_device_save( satip_device_t *sd )
{
satip_frontend_t *lfe;
htsmsg_t *m, *l;
m = htsmsg_create_map();
idnode_save(&sd->th_id, m);
l = htsmsg_create_map();
TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link)
satip_frontend_save(lfe, l);
htsmsg_add_msg(m, "frontends", l);
hts_settings_save(m, "input/satip/adapters/%s",
idnode_uuid_as_str(&sd->th_id));
htsmsg_destroy(m);
}
void
satip_device_destroy( satip_device_t *sd )
{
satip_frontend_t *lfe;
lock_assert(&global_lock);
gtimer_disarm(&sd->sd_destroy_timer);
while ((lfe = TAILQ_FIRST(&sd->sd_frontends)) != NULL)
satip_frontend_delete(lfe);
#define FREEM(x) free(sd->sd_info.x)
FREEM(myaddr);
FREEM(addr);
FREEM(uuid);
FREEM(bootid);
FREEM(configid);
FREEM(deviceid);
FREEM(location);
FREEM(server);
FREEM(friendlyname);
FREEM(manufacturer);
FREEM(manufacturerURL);
FREEM(modeldesc);
FREEM(modelname);
FREEM(modelnum);
FREEM(serialnum);
FREEM(presentation);
FREEM(tunercfg);
#undef FREEM
tvh_hardware_delete((tvh_hardware_t*)sd);
free(sd);
}
static void
satip_device_destroy_cb( void *aux )
{
satip_device_destroy((satip_device_t *)aux);
satip_device_discovery_start();
}
void
satip_device_destroy_later( satip_device_t *sd, int after )
{
gtimer_arm_ms(&sd->sd_destroy_timer, satip_device_destroy_cb, sd, after);
}
/*
* Discovery job
*/
typedef struct satip_discovery {
TAILQ_ENTRY(satip_discovery) disc_link;
char *myaddr;
char *location;
char *server;
char *uuid;
char *bootid;
char *configid;
char *deviceid;
url_t url;
http_client_t *http_client;
time_t http_start;
} satip_discovery_t;
TAILQ_HEAD(satip_discovery_queue, satip_discovery);
static int satip_discoveries_count;
static struct satip_discovery_queue satip_discoveries;
static upnp_service_t *satip_discovery_service;
static gtimer_t satip_discovery_timer;
static gtimer_t satip_discovery_timerq;
static str_list_t *satip_static_clients;
static void
satip_discovery_destroy(satip_discovery_t *d, int unlink)
{
if (d == NULL)
return;
if (unlink) {
satip_discoveries_count--;
TAILQ_REMOVE(&satip_discoveries, d, disc_link);
}
if (d->http_client)
http_client_close(d->http_client);
urlreset(&d->url);
free(d->myaddr);
free(d->location);
free(d->server);
free(d->uuid);
free(d->bootid);
free(d->configid);
free(d->deviceid);
free(d);
}
static satip_discovery_t *
satip_discovery_find(satip_discovery_t *d)
{
satip_discovery_t *sd;
TAILQ_FOREACH(sd, &satip_discoveries, disc_link)
if (strcmp(sd->uuid, d->uuid) == 0)
return sd;
return NULL;
}
static void
satip_discovery_http_closed(http_client_t *hc, int errn)
{
satip_discovery_t *d = hc->hc_aux;
char *s;
htsmsg_t *xml = NULL, *tags, *root, *device;
const char *friendlyname, *manufacturer, *manufacturerURL, *modeldesc;
const char *modelname, *modelnum, *serialnum;
const char *presentation, *tunercfg, *udn, *uuid;
const char *cs;
satip_device_info_t info;
char errbuf[100];
char *argv[10];
int i, n;
s = http_arg_get(&hc->hc_args, "Content-Type");
if (s && strcasecmp(s, "text/xml")) {
errn = EMEDIUMTYPE;
s = NULL;
}
if (errn != 0 || s == NULL || hc->hc_code != 200 ||
hc->hc_data_size == 0 || hc->hc_data == NULL) {
tvhlog(LOG_ERR, "satip", "Cannot get %s: %s", d->location, strerror(errn));
return;
}
#if ENABLE_TRACE
tvhtrace("satip", "received XML description from %s", hc->hc_host);
tvhlog_hexdump("satip", hc->hc_data, hc->hc_data_size);
#endif
s = hc->hc_data + hc->hc_data_size - 1;
while (s != hc->hc_data && *s != '/')
s--;
if (s != hc->hc_data)
s--;
if (strncmp(s, "</root>", 7))
return;
/* Parse */
xml = htsmsg_xml_deserialize(hc->hc_data, errbuf, sizeof(errbuf));
hc->hc_data = NULL;
if (!xml) {
tvhlog(LOG_ERR, "satip_discovery_desc", "htsmsg_xml_deserialize error %s", errbuf);
goto finish;
}
if ((tags = htsmsg_get_map(xml, "tags")) == NULL)
goto finish;
if ((root = htsmsg_get_map(tags, "root")) == NULL)
goto finish;
if ((device = htsmsg_get_map(root, "tags")) == NULL)
goto finish;
if ((device = htsmsg_get_map(device, "device")) == NULL)
goto finish;
if ((device = htsmsg_get_map(device, "tags")) == NULL)
goto finish;
if ((cs = htsmsg_xml_get_cdata_str(device, "deviceType")) == NULL)
goto finish;
if (strcmp(cs, "urn:ses-com:device:SatIPServer:1"))
goto finish;
if ((friendlyname = htsmsg_xml_get_cdata_str(device, "friendlyName")) == NULL)
goto finish;
if ((manufacturer = htsmsg_xml_get_cdata_str(device, "manufacturer")) == NULL)
goto finish;
if ((manufacturerURL = htsmsg_xml_get_cdata_str(device, "manufacturerURL")) == NULL)
goto finish;
if ((modeldesc = htsmsg_xml_get_cdata_str(device, "modelDescription")) == NULL)
goto finish;
if ((modelname = htsmsg_xml_get_cdata_str(device, "modelName")) == NULL)
goto finish;
if ((modelnum = htsmsg_xml_get_cdata_str(device, "modelNumber")) == NULL)
goto finish;
if ((serialnum = htsmsg_xml_get_cdata_str(device, "serialNumber")) == NULL)
goto finish;
if ((presentation = htsmsg_xml_get_cdata_str(device, "presentationURL")) == NULL)
goto finish;
if ((udn = htsmsg_xml_get_cdata_str(device, "UDN")) == NULL)
goto finish;
if ((tunercfg = htsmsg_xml_get_cdata_str(device, "urn:ses-com:satipX_SATIPCAP")) == NULL)
goto finish;
uuid = NULL;
n = http_tokenize((char *)udn, argv, ARRAY_SIZE(argv), ':');
for (i = 0; i < n+1; i++)
if (argv[i] && strcmp(argv[i], "uuid") == 0) {
uuid = argv[++i];
break;
}
if (uuid == NULL || (d->uuid[0] && strcmp(uuid, d->uuid)))
goto finish;
info.myaddr = strdup(d->myaddr);
info.addr = strdup(d->url.host);
info.uuid = strdup(uuid);
info.bootid = strdup(d->bootid);
info.configid = strdup(d->configid);
info.deviceid = strdup(d->deviceid);
info.location = strdup(d->location);
info.server = strdup(d->server);
info.friendlyname = strdup(friendlyname);
info.manufacturer = strdup(manufacturer);
info.manufacturerURL = strdup(manufacturerURL);
info.modeldesc = strdup(modeldesc);
info.modelname = strdup(modelname);
info.modelnum = strdup(modelnum);
info.serialnum = strdup(serialnum);
info.presentation = strdup(presentation);
info.tunercfg = strdup(tunercfg);
htsmsg_destroy(xml);
xml = NULL;
pthread_mutex_lock(&global_lock);
if (!satip_device_find(info.uuid))
satip_device_create(&info);
pthread_mutex_unlock(&global_lock);
free(info.myaddr);
free(info.location);
free(info.server);
free(info.addr);
free(info.uuid);
free(info.bootid);
free(info.configid);
free(info.deviceid);
free(info.friendlyname);
free(info.manufacturer);
free(info.manufacturerURL);
free(info.modeldesc);
free(info.modelname);
free(info.modelnum);
free(info.serialnum);
free(info.presentation);
free(info.tunercfg);
finish:
htsmsg_destroy(xml);
}
static void
satip_discovery_timerq_cb(void *aux)
{
satip_discovery_t *d, *next;
int r;
lock_assert(&global_lock);
next = TAILQ_FIRST(&satip_discoveries);
while (next) {
d = next;
next = TAILQ_NEXT(d, disc_link);
if (d->http_client) {
if (dispatch_clock - d->http_start > 4)
satip_discovery_destroy(d, 1);
continue;
}
d->http_client = http_client_connect(d, HTTP_VERSION_1_1, d->url.scheme,
d->url.host, d->url.port);
if (d->http_client == NULL)
satip_discovery_destroy(d, 1);
else {
d->http_start = dispatch_clock;
d->http_client->hc_conn_closed = satip_discovery_http_closed;
http_client_register(d->http_client);
r = http_client_simple(d->http_client, &d->url);
if (r < 0)
satip_discovery_destroy(d, 1);
}
}
if (TAILQ_FIRST(&satip_discoveries))
gtimer_arm(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 5);
}
static void
satip_discovery_service_received
(uint8_t *data, size_t len, udp_connection_t *conn,
struct sockaddr_storage *storage)
{
char *buf, *ptr, *saveptr;
char *argv[10];
char *st = NULL;
char *location = NULL;
char *server = NULL;
char *uuid = NULL;
char *bootid = NULL;
char *configid = NULL;
char *deviceid = NULL;
char sockbuf[128];
satip_discovery_t *d;
int n, i;
if (len > 8191 || satip_discoveries_count > 100)
return;
buf = alloca(len+1);
memcpy(buf, data, len);
buf[len] = '\0';
ptr = strtok_r(buf, "\r\n", &saveptr);
/* Request decoder */
if (ptr) {
if (http_tokenize(ptr, argv, 3, -1) != 3)
return;
if (conn->multicast) {
if (strcmp(argv[0], "NOTIFY"))
return;
if (strcmp(argv[1], "*"))
return;
if (strcmp(argv[2], "HTTP/1.1"))
return;
} else {
if (strcmp(argv[0], "HTTP/1.1"))
return;
if (strcmp(argv[1], "200"))
return;
}
ptr = strtok_r(NULL, "\r\n", &saveptr);
}
/* Header decoder */
while (1) {
if (ptr == NULL)
break;
if (http_tokenize(ptr, argv, 2, -1) == 2) {
if (strcmp(argv[0], "ST:") == 0)
st = argv[1];
else if (strcmp(argv[0], "LOCATION:") == 0)
location = argv[1];
else if (strcmp(argv[0], "SERVER:") == 0)
server = argv[1];
else if (strcmp(argv[0], "BOOTID.UPNP.ORG:") == 0)
bootid = argv[1];
else if (strcmp(argv[0], "CONFIGID.UPNP.ORG:") == 0)
configid = argv[1];
else if (strcmp(argv[0], "DEVICEID.SES.COM:") == 0)
deviceid = argv[1];
else if (strcmp(argv[0], "USN:") == 0) {
n = http_tokenize(argv[1], argv, ARRAY_SIZE(argv), ':');
for (i = 0; i < n+1; i++)
if (argv[i] && strcmp(argv[i], "uuid") == 0) {
uuid = argv[++i];
break;
}
}
}
ptr = strtok_r(NULL, "\r\n", &saveptr);
}
/* Sanity checks */
if (st == NULL || strcmp(st, "urn:ses-com:device:SatIPServer:1"))
return;
if (uuid == NULL && strlen(uuid) < 16)
return;
if (location == NULL && strncmp(location, "http://", 7))
return;
if (bootid == NULL || configid == NULL || server == NULL)
return;
/* Forward information to next layer */
d = calloc(1, sizeof(satip_discovery_t));
if (inet_ntop(storage->ss_family, IP_IN_ADDR(conn->ip),
sockbuf, sizeof(sockbuf)) == NULL) {
satip_discovery_destroy(d, 0);
return;
}
d->myaddr = strdup(sockbuf);
d->location = strdup(location);
d->server = strdup(server);
d->uuid = strdup(uuid);
d->bootid = strdup(bootid);
d->configid = strdup(configid);
d->deviceid = strdup(deviceid ? deviceid : "");
if (urlparse(d->location, &d->url)) {
satip_discovery_destroy(d, 0);
return;
}
pthread_mutex_lock(&global_lock);
i = 1;
if (!satip_discovery_find(d) && !satip_device_find(d->uuid)) {
TAILQ_INSERT_TAIL(&satip_discoveries, d, disc_link);
satip_discoveries_count++;
gtimer_arm_ms(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 250);
i = 0;
}
pthread_mutex_unlock(&global_lock);
if (i) /* duplicate */
satip_discovery_destroy(d, 0);
}
static void
satip_discovery_static(const char *descurl)
{
satip_discovery_t *d;
lock_assert(&global_lock);
if (satip_device_find_by_descurl(descurl))
return;
d = calloc(1, sizeof(satip_discovery_t));
if (urlparse(descurl, &d->url)) {
satip_discovery_destroy(d, 0);
return;
}
d->myaddr = strdup(d->url.host);
d->location = strdup(descurl);
d->server = strdup("");
d->uuid = strdup("");
d->bootid = strdup("");
d->configid = strdup("");
d->deviceid = strdup("");
TAILQ_INSERT_TAIL(&satip_discoveries, d, disc_link);
satip_discoveries_count++;
satip_discovery_timerq_cb(NULL);
}
static void
satip_discovery_service_destroy(upnp_service_t *us)
{
satip_discovery_service = NULL;
}
static void
satip_discovery_timer_cb(void *aux)
{
#define MSG "\
M-SEARCH * HTTP/1.1\r\n\
HOST: 239.255.255.250:1900\r\n\
MAN: \"ssdp:discover\"\r\n\
MX: 2\r\n\
ST: urn:ses-com:device:SatIPServer:1\r\n\
\r\n"
int i;
if (!tvheadend_running)
return;
if (!upnp_running) {
gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1);
return;
}
if (satip_discovery_service == NULL) {
satip_discovery_service = upnp_service_create(upnp_service);
if (satip_discovery_service) {
satip_discovery_service->us_received = satip_discovery_service_received;
satip_discovery_service->us_destroy = satip_discovery_service_destroy;
}
}
if (satip_discovery_service) {
htsbuf_queue_t q;
htsbuf_queue_init(&q, 0);
htsbuf_append(&q, MSG, sizeof(MSG)-1);
upnp_send(&q, NULL);
htsbuf_queue_flush(&q);
}
for (i = 0; i < satip_static_clients->num; i++)
satip_discovery_static(satip_static_clients->str[i]);
gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 3600);
#undef MSG
}
static void
satip_device_discovery_start( void )
{
gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1);
}
/*
* Initialization
*/
void satip_init ( str_list_t *clients )
{
TAILQ_INIT(&satip_discoveries);
satip_static_clients = clients;
satip_device_discovery_start();
}
void satip_done ( void )
{
tvh_hardware_t *th, *n;
satip_discovery_t *d, *nd;
pthread_mutex_lock(&global_lock);
for (th = LIST_FIRST(&tvh_hardware); th != NULL; th = n) {
n = LIST_NEXT(th, th_link);
if (idnode_is_instance(&th->th_id, &satip_device_class)) {
satip_device_destroy((satip_device_t *)th);
}
}
for (d = TAILQ_FIRST(&satip_discoveries); d != NULL; d = nd) {
nd = TAILQ_NEXT(d, disc_link);
satip_discovery_destroy(d, 1);
}
pthread_mutex_unlock(&global_lock);
}

View file

@ -0,0 +1,26 @@
/*
* Tvheadend - SAT-IP DVB private data
*
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TVH_SATIP_H__
#define __TVH_SATIP_H__
void satip_init( str_list_t *clients );
void satip_done( void );
#endif /* __TVH_SATIP_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,218 @@
/*
* Tvheadend - SAT-IP DVB private data
*
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TVH_SATIP_PRIVATE_H__
#define __TVH_SATIP_PRIVATE_H__
#include "input.h"
#include "htsbuf.h"
#include "udp.h"
#include "http.h"
#include "satip.h"
#define SATIP_BUF_SIZE (4000*188)
typedef struct satip_device_info satip_device_info_t;
typedef struct satip_device satip_device_t;
typedef struct satip_frontend satip_frontend_t;
typedef struct satip_satconf satip_satconf_t;
struct satip_device_info
{
char *myaddr; /* IP address of this host received data from the SAT>IP device */
char *addr; /* IP address */
char *uuid;
char *bootid;
char *configid;
char *deviceid;
char *location; /*< URL of the XML file */
char *server;
char *friendlyname;
char *manufacturer;
char *manufacturerURL;
char *modeldesc;
char *modelname;
char *modelnum;
char *serialnum;
char *presentation;
char *tunercfg; /*< XML urn:ses-com:satipX_SATIPCAP contents */
};
struct satip_device
{
tvh_hardware_t;
gtimer_t sd_destroy_timer;
/*
* Adapter info
*/
satip_device_info_t sd_info;
/*
* Frontends
*/
TAILQ_HEAD(,satip_frontend) sd_frontends;
/*
* RTSP
*/
int sd_fullmux_ok;
int sd_pids_max;
int sd_pids_len;
int sd_pids_deladd;
int sd_sig_scale;
int sd_pids0;
};
struct satip_frontend
{
mpegts_input_t;
/*
* Device
*/
satip_device_t *sf_device;
TAILQ_ENTRY(satip_frontend) sf_link;
/*
* Frontend info
*/
int sf_number;
dvb_fe_type_t sf_type;
int sf_type_t2;
char *sf_type_override;
int sf_master;
int sf_udp_rtp_port;
int sf_play2;
/*
* Reception
*/
pthread_t sf_dvr_thread;
th_pipe_t sf_dvr_pipe;
pthread_mutex_t sf_dvr_lock;
pthread_cond_t sf_dvr_cond;
uint16_t *sf_pids;
uint16_t *sf_pids_tuned;
int sf_pids_any;
int sf_pids_any_tuned;
int sf_pids_size;
int sf_pids_count;
int sf_pids_tcount; /*< tuned count */
int sf_running;
int sf_position;
udp_connection_t *sf_rtp;
udp_connection_t *sf_rtcp;
int sf_rtp_port;
mpegts_mux_instance_t *sf_mmi;
signal_state_t sf_status;
gtimer_t sf_monitor_timer;
/*
* Configuration
*/
int sf_positions;
TAILQ_HEAD(,satip_satconf) sf_satconf;
};
struct satip_satconf
{
idnode_t sfc_id;
/*
* Parent
*/
satip_frontend_t *sfc_lfe;
TAILQ_ENTRY(satip_satconf) sfc_link;
/*
* Config
*/
int sfc_enabled;
int sfc_position;
int sfc_priority;
char *sfc_name;
/*
* Assigned networks to this SAT configuration
*/
idnode_set_t *sfc_networks;
};
/*
* Methods
*/
void satip_device_init ( void );
void satip_device_done ( void );
void satip_device_save ( satip_device_t *sd );
void satip_device_destroy ( satip_device_t *sd );
void satip_device_destroy_later( satip_device_t *sd, int after_ms );
satip_frontend_t *
satip_frontend_create
( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num );
void satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *m );
void satip_frontend_delete ( satip_frontend_t *lfe );
/*
* SAT>IP Satconf configuration
*/
void satip_satconf_save ( satip_frontend_t *lfe, htsmsg_t *m );
void satip_satconf_destroy ( satip_frontend_t *lfe );
void satip_satconf_create
( satip_frontend_t *lfe, htsmsg_t *conf );
void satip_satconf_updated_positions
( satip_frontend_t *lfe );
int satip_satconf_get_priority
( satip_frontend_t *lfe, mpegts_mux_t *mm );
int satip_satconf_get_position
( satip_frontend_t *lfe, mpegts_mux_t *mm );
/*
* RTSP part
*/
#define SATIP_SETUP_PLAY (1<<0)
#define SATIP_SETUP_PIDS0 (1<<1)
int
satip_rtsp_setup( http_client_t *hc,
int src, int fe, int udp_port,
const dvb_mux_conf_t *dmc,
int pids0 );
int
satip_rtsp_play( http_client_t *hc, const char *pids,
const char *addpids, const char *delpids,
int max_pids_len );
#endif /* __TVH_SATIP_PRIVATE_H__ */

View file

@ -0,0 +1,282 @@
/*
* Tvheadend - SAT>IP DVB RTSP client
*
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <signal.h>
#include "tvheadend.h"
#include "htsbuf.h"
#include "tcp.h"
#include "http.h"
#include "satip_private.h"
/*
*
*/
typedef struct tvh2satip {
int t; ///< TVH internal value
const char *s; ///< SATIP API value
} tvh2satip_t;
#define TABLE_EOD -1
static const char *
satip_rtsp_setup_find(const char *prefix, tvh2satip_t *tbl,
int src, const char *defval)
{
while (tbl->t >= 0) {
if (tbl->t == src)
return tbl->s;
tbl++;
}
tvhtrace("satip", "%s - cannot translate %d", prefix, src);
return defval;
}
#define ADD(s, d, def) \
strcat(buf, "&" #d "="), strcat(buf, satip_rtsp_setup_find(#d, d, dmc->s, def))
static void
satip_rtsp_add_val(const char *name, char *buf, uint32_t val)
{
char sec[5];
sprintf(buf + strlen(buf), "&%s=%i", name, val / 1000);
if (val % 1000) {
sprintf(sec, ".%03i", val % 1000);
if (sec[3] == '0') {
sec[3] = '\0';
if (sec[2] == '0')
sec[2] = '\0';
}
}
}
int
satip_rtsp_setup( http_client_t *hc, int src, int fe,
int udp_port, const dvb_mux_conf_t *dmc, int flags )
{
static tvh2satip_t msys[] = {
{ .t = DVB_SYS_DVBT, "dvbt" },
{ .t = DVB_SYS_DVBT2, "dvbt2" },
{ .t = DVB_SYS_DVBS, "dvbs" },
{ .t = DVB_SYS_DVBS2, "dvbs2" },
{ .t = DVB_SYS_DVBC_ANNEX_A, "dvbc" },
{ .t = DVB_SYS_DVBC_ANNEX_B, "dvbc" },
{ .t = DVB_SYS_DVBC_ANNEX_C, "dvbc" },
{ .t = TABLE_EOD }
};
static tvh2satip_t pol[] = {
{ .t = DVB_POLARISATION_HORIZONTAL, "h" },
{ .t = DVB_POLARISATION_VERTICAL, "v" },
{ .t = DVB_POLARISATION_CIRCULAR_LEFT, "l" },
{ .t = DVB_POLARISATION_CIRCULAR_RIGHT, "r" },
{ .t = TABLE_EOD }
};
static tvh2satip_t ro[] = {
{ .t = DVB_ROLLOFF_AUTO, "0.35" },
{ .t = DVB_ROLLOFF_20, "0.20" },
{ .t = DVB_ROLLOFF_25, "0.25" },
{ .t = DVB_ROLLOFF_35, "0.35" },
{ .t = TABLE_EOD }
};
static tvh2satip_t mtype[] = {
{ .t = DVB_MOD_AUTO, "auto" },
{ .t = DVB_MOD_QAM_16, "16qam" },
{ .t = DVB_MOD_QAM_32, "32qam" },
{ .t = DVB_MOD_QAM_64, "64qam" },
{ .t = DVB_MOD_QAM_128, "128qam"},
{ .t = DVB_MOD_QAM_256, "256qam"},
{ .t = DVB_MOD_QPSK, "qpsk" },
{ .t = DVB_MOD_PSK_8, "8psk" },
{ .t = TABLE_EOD }
};
static tvh2satip_t plts[] = {
{ .t = DVB_PILOT_AUTO, "auto" },
{ .t = DVB_PILOT_ON, "on" },
{ .t = DVB_PILOT_OFF, "off" },
{ .t = TABLE_EOD }
};
static tvh2satip_t fec[] = {
{ .t = DVB_FEC_AUTO, "auto" },
{ .t = DVB_FEC_1_2, "12" },
{ .t = DVB_FEC_2_3, "23" },
{ .t = DVB_FEC_3_4, "34" },
{ .t = DVB_FEC_3_5, "35" },
{ .t = DVB_FEC_4_5, "45" },
{ .t = DVB_FEC_5_6, "56" },
{ .t = DVB_FEC_7_8, "78" },
{ .t = DVB_FEC_8_9, "89" },
{ .t = DVB_FEC_9_10, "910" },
{ .t = TABLE_EOD }
};
static tvh2satip_t tmode[] = {
{ .t = DVB_TRANSMISSION_MODE_AUTO, "auto" },
{ .t = DVB_TRANSMISSION_MODE_1K, "1k" },
{ .t = DVB_TRANSMISSION_MODE_2K, "2k" },
{ .t = DVB_TRANSMISSION_MODE_4K, "4k" },
{ .t = DVB_TRANSMISSION_MODE_8K, "8k" },
{ .t = DVB_TRANSMISSION_MODE_16K, "16k" },
{ .t = DVB_TRANSMISSION_MODE_32K, "32k" },
{ .t = TABLE_EOD }
};
static tvh2satip_t gi[] = {
{ .t = DVB_GUARD_INTERVAL_AUTO, "auto" },
{ .t = DVB_GUARD_INTERVAL_1_4, "14" },
{ .t = DVB_GUARD_INTERVAL_1_8, "18" },
{ .t = DVB_GUARD_INTERVAL_1_16, "116" },
{ .t = DVB_GUARD_INTERVAL_1_32, "132" },
{ .t = DVB_GUARD_INTERVAL_1_128, "1128" },
{ .t = DVB_GUARD_INTERVAL_19_128, "19128" },
{ .t = DVB_GUARD_INTERVAL_19_256, "19256" },
{ .t = TABLE_EOD }
};
char buf[512];
char *stream = NULL;
char _stream[32];
if (src > 0)
sprintf(buf, "src=%i&", src);
else
buf[0] = '\0';
sprintf(buf + strlen(buf), "fe=%i", fe);
satip_rtsp_add_val("freq", buf, dmc->dmc_fe_freq);
if (dmc->dmc_fe_delsys == DVB_SYS_DVBS ||
dmc->dmc_fe_delsys == DVB_SYS_DVBS2) {
satip_rtsp_add_val("sr", buf, dmc->u.dmc_fe_qpsk.symbol_rate);
ADD(dmc_fe_delsys, msys, "dvbs");
if (dmc->dmc_fe_modulation != DVB_MOD_NONE &&
dmc->dmc_fe_modulation != DVB_MOD_AUTO)
ADD(dmc_fe_modulation, mtype, "qpsk");
ADD(u.dmc_fe_qpsk.polarisation, pol, "h" );
if (dmc->u.dmc_fe_qpsk.fec_inner != DVB_FEC_NONE &&
dmc->u.dmc_fe_qpsk.fec_inner != DVB_FEC_AUTO)
ADD(u.dmc_fe_qpsk.fec_inner, fec, "auto");
if (dmc->dmc_fe_rolloff != DVB_ROLLOFF_NONE &&
dmc->dmc_fe_rolloff != DVB_ROLLOFF_AUTO)
ADD(dmc_fe_rolloff, ro, "0.35");
if (dmc->dmc_fe_pilot != DVB_PILOT_NONE &&
dmc->dmc_fe_pilot != DVB_PILOT_AUTO)
ADD(dmc_fe_pilot, plts, "auto");
} else if (dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_A ||
dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_B ||
dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_C) {
satip_rtsp_add_val("sr", buf, dmc->u.dmc_fe_qam.symbol_rate);
ADD(dmc_fe_delsys, msys, "dvbc");
ADD(dmc_fe_modulation, mtype, "64qam");
/* missing plp */
if (dmc->u.dmc_fe_qam.fec_inner != DVB_FEC_NONE &&
dmc->u.dmc_fe_qam.fec_inner != DVB_FEC_AUTO)
/* note: OctopusNet device does not handle 'fec=auto' */
ADD(u.dmc_fe_qam.fec_inner, fec, "auto");
} else {
if (dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_AUTO &&
dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_NONE)
satip_rtsp_add_val("bw", buf, dmc->u.dmc_fe_ofdm.bandwidth);
ADD(dmc_fe_delsys, msys, "dvbt");
if (dmc->dmc_fe_modulation != DVB_MOD_AUTO &&
dmc->dmc_fe_modulation != DVB_MOD_NONE &&
dmc->dmc_fe_modulation != DVB_MOD_QAM_AUTO)
ADD(dmc_fe_modulation, mtype, "64qam");
if (dmc->u.dmc_fe_ofdm.transmission_mode != DVB_TRANSMISSION_MODE_AUTO &&
dmc->u.dmc_fe_ofdm.transmission_mode != DVB_TRANSMISSION_MODE_NONE)
ADD(u.dmc_fe_ofdm.transmission_mode, tmode, "8k");
if (dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_AUTO &&
dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_NONE)
ADD(u.dmc_fe_ofdm.guard_interval, gi, "18");
}
if (flags & SATIP_SETUP_PIDS0)
strcat(buf, "&pids=0");
tvhtrace("satip", "setup params - %s", buf);
if (hc->hc_rtsp_stream_id >= 0)
snprintf(stream = _stream, sizeof(_stream), "/stream=%li",
hc->hc_rtsp_stream_id);
if (flags & SATIP_SETUP_PLAY)
return rtsp_play(hc, stream, buf);
return rtsp_setup(hc, stream, buf, NULL, udp_port, udp_port + 1);
}
static const char *
satip_rtsp_pids_strip( const char *s, int maxlen )
{
char *ptr;
if (s == NULL)
return NULL;
while (*s == ',')
s++;
while (strlen(s) > maxlen) {
ptr = rindex(s, ',');
if (ptr == NULL)
abort();
*ptr = '\0';
}
if (*s == '\0')
return NULL;
return s;
}
int
satip_rtsp_play( http_client_t *hc, const char *pids,
const char *addpids, const char *delpids,
int max_pids_len )
{
htsbuf_queue_t q;
char *stream = NULL;
char _stream[32];
char *query;
int r, split = 0;
pids = satip_rtsp_pids_strip(pids , max_pids_len);
addpids = satip_rtsp_pids_strip(addpids, max_pids_len);
delpids = satip_rtsp_pids_strip(delpids, max_pids_len);
if (pids == NULL && addpids == NULL && delpids == NULL)
return -EINVAL;
//printf("pids = '%s' addpids = '%s' delpids = '%s'\n", pids, addpids, delpids);
htsbuf_queue_init(&q, 0);
/* pids setup and add/del requests cannot be mixed per specification */
if (pids) {
htsbuf_qprintf(&q, "pids=%s", pids);
} else {
if (delpids)
htsbuf_qprintf(&q, "delpids=%s", delpids);
if (addpids) {
if (delpids) {
/* try to maintain the maximum request size - simple split */
if (strlen(addpids) + strlen(delpids) <= max_pids_len)
split = 1;
else
htsbuf_append(&q, "&", 1);
}
if (!split)
htsbuf_qprintf(&q, "addpids=%s", addpids);
}
}
if (hc->hc_rtsp_stream_id >= 0)
snprintf(stream = _stream, sizeof(_stream), "/stream=%li",
hc->hc_rtsp_stream_id);
query = htsbuf_to_string(&q);
r = rtsp_play(hc, stream, query);
free(query);
return r;
}

View file

@ -0,0 +1,327 @@
/*
* Tvheadend - SAT>IP DVB satconf
*
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tvheadend.h"
#include "satip_private.h"
#include "settings.h"
/* **************************************************************************
* Frontend callbacks
* *************************************************************************/
static satip_satconf_t *
satip_satconf_find_ele( satip_frontend_t *lfe, mpegts_mux_t *mux )
{
satip_satconf_t *sfc;
TAILQ_FOREACH(sfc, &lfe->sf_satconf, sfc_link) {
if (idnode_set_exists(sfc->sfc_networks, &mux->mm_network->mn_id))
return sfc;
}
return NULL;
}
int
satip_satconf_get_priority
( satip_frontend_t *lfe, mpegts_mux_t *mm )
{
satip_satconf_t *sfc = satip_satconf_find_ele(lfe, mm);
return sfc ? sfc->sfc_priority : 0;
}
int
satip_satconf_get_position
( satip_frontend_t *lfe, mpegts_mux_t *mm )
{
satip_satconf_t *sfc = satip_satconf_find_ele(lfe, mm);
return sfc ? sfc->sfc_position : 0;
}
/* **************************************************************************
* Class definition
* *************************************************************************/
static const void *
satip_satconf_class_network_get( void *o )
{
satip_satconf_t *sfc = o;
return idnode_set_as_htsmsg(sfc->sfc_networks);
}
static int
satip_satconf_class_network_set( void *o, const void *p )
{
satip_satconf_t *sfc = o;
const htsmsg_t *msg = p;
mpegts_network_t *mn;
idnode_set_t *n = idnode_set_create();
htsmsg_field_t *f;
const char *str;
int i, save;
HTSMSG_FOREACH(f, msg) {
if (!(str = htsmsg_field_get_str(f))) continue;
if (!(mn = mpegts_network_find(str))) continue;
idnode_set_add(n, &mn->mn_id, NULL);
}
save = n->is_count != sfc->sfc_networks->is_count;
if (!save) {
for (i = 0; i < n->is_count; i++)
if (!idnode_set_exists(sfc->sfc_networks, n->is_array[i])) {
save = 1;
break;
}
}
if (save) {
/* update the local (antenna satconf) network list */
idnode_set_free(sfc->sfc_networks);
sfc->sfc_networks = n;
/* update the input (frontend) network list */
htsmsg_t *l = htsmsg_create_list();
satip_frontend_t *lfe = sfc->sfc_lfe, *lfe2;
satip_satconf_t *sfc2;
TAILQ_FOREACH(sfc2, &lfe->sf_satconf, sfc_link) {
for (i = 0; i < sfc2->sfc_networks->is_count; i++)
htsmsg_add_str(l, NULL,
idnode_uuid_as_str(sfc2->sfc_networks->is_array[i]));
}
mpegts_input_class_network_set(lfe, l);
/* update the slave tuners, too */
TAILQ_FOREACH(lfe2, &lfe->sf_device->sd_frontends, sf_link)
if (lfe2->sf_number != lfe->sf_number &&
lfe2->sf_master == lfe->sf_number)
mpegts_input_class_network_set(lfe2, l);
htsmsg_destroy(l);
} else {
idnode_set_free(n);
}
return save;
}
static htsmsg_t *
satip_satconf_class_network_enum( void *o )
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_t *p = htsmsg_create_map();
htsmsg_add_str(m, "type", "api");
htsmsg_add_str(m, "uri", "idnode/load");
htsmsg_add_str(m, "event", "mpegts_network");
htsmsg_add_u32(p, "enum", 1);
htsmsg_add_str(p, "class", dvb_network_dvbs_class.ic_class);
htsmsg_add_msg(m, "params", p);
return m;
}
static char *
satip_satconf_class_network_rend( void *o )
{
satip_satconf_t *sfc = o;
htsmsg_t *l = idnode_set_as_htsmsg(sfc->sfc_networks);
char *str = htsmsg_list_2_csv(l);
htsmsg_destroy(l);
return str;
}
static const char *
satip_satconf_class_get_title ( idnode_t *o )
{
return ((satip_satconf_t *)o)->sfc_name;
}
static void
satip_satconf_class_save ( idnode_t *in )
{
satip_satconf_t *sfc = (satip_satconf_t*)in;
satip_device_save(sfc->sfc_lfe->sf_device);
}
const idclass_t satip_satconf_class =
{
.ic_class = "satip_satconf",
.ic_caption = "Satconf",
.ic_get_title = satip_satconf_class_get_title,
.ic_save = satip_satconf_class_save,
.ic_properties = (const property_t[]) {
{
.type = PT_BOOL,
.id = "enabled",
.name = "Enabled",
.off = offsetof(satip_satconf_t, sfc_enabled),
},
{
.type = PT_STR,
.id = "displayname",
.name = "Name",
.off = offsetof(satip_satconf_t, sfc_name),
.notify = idnode_notify_title_changed,
},
{
.type = PT_INT,
.id = "priority",
.name = "Priority",
.off = offsetof(satip_satconf_t, sfc_priority),
.opts = PO_ADVANCED,
},
{
.type = PT_INT,
.id = "position",
.name = "Position",
.off = offsetof(satip_satconf_t, sfc_position),
.def.i = 1,
.opts = PO_RDONLY | PO_ADVANCED,
},
{
.type = PT_STR,
.id = "networks",
.name = "Networks",
.islist = 1,
.set = satip_satconf_class_network_set,
.get = satip_satconf_class_network_get,
.list = satip_satconf_class_network_enum,
.rend = satip_satconf_class_network_rend,
},
{}
}
};
/* **************************************************************************
* Creation/Config
* *************************************************************************/
static satip_satconf_t *
satip_satconf_create0
( satip_frontend_t *lfe, htsmsg_t *conf, int position )
{
static const char *tbl[] = {" (AA)", " (AB)", " (BA)", " (BB)"};
const char *uuid = NULL;
satip_satconf_t *sfc = calloc(1, sizeof(*sfc));
char buf[32];
const char *s;
/* defaults */
sfc->sfc_priority = 1;
if (conf)
uuid = htsmsg_get_str(conf, "uuid");
if (idnode_insert(&sfc->sfc_id, uuid, &satip_satconf_class)) {
free(sfc);
return NULL;
}
sfc->sfc_networks = idnode_set_create();
sfc->sfc_lfe = lfe;
sfc->sfc_position = position + 1;
TAILQ_INSERT_TAIL(&lfe->sf_satconf, sfc, sfc_link);
if (conf)
idnode_load(&sfc->sfc_id, conf);
if (sfc->sfc_name == NULL || sfc->sfc_name[0] == '\0') {
free(sfc->sfc_name);
s = position < 4 ? tbl[position] : "";
snprintf(buf, sizeof(buf), "Position #%i%s", position + 1, s);
sfc->sfc_name = strdup(buf);
}
return sfc;
}
void
satip_satconf_create
( satip_frontend_t *lfe, htsmsg_t *conf )
{
htsmsg_t *l, *e;
htsmsg_field_t *f;
int pos = 0;
if (conf && (l = htsmsg_get_list(conf, "satconf"))) {
satip_satconf_destroy(lfe);
HTSMSG_FOREACH(f, l) {
if (!(e = htsmsg_field_get_map(f))) continue;
if (satip_satconf_create0(lfe, e, pos++))
lfe->sf_positions++;
}
}
if (lfe->sf_positions == 0)
for ( ; lfe->sf_positions < 4; lfe->sf_positions++)
satip_satconf_create0(lfe, NULL, lfe->sf_positions);
}
static void
satip_satconf_destroy0
( satip_satconf_t *sfc )
{
satip_frontend_t *lfe = sfc->sfc_lfe;
TAILQ_REMOVE(&lfe->sf_satconf, sfc, sfc_link);
idnode_unlink(&sfc->sfc_id);
idnode_set_free(sfc->sfc_networks);
free(sfc->sfc_name);
free(sfc);
}
void
satip_satconf_updated_positions
( satip_frontend_t *lfe )
{
satip_satconf_t *sfc, *sfc_old;
int i;
sfc = TAILQ_FIRST(&lfe->sf_satconf);
for (i = 0; i < lfe->sf_positions; i++) {
if (sfc == NULL)
satip_satconf_create0(lfe, NULL, i);
sfc = sfc ? TAILQ_NEXT(sfc, sfc_link) : NULL;
}
while (sfc) {
sfc_old = sfc;
sfc = TAILQ_NEXT(sfc, sfc_link);
satip_satconf_destroy0(sfc_old);
}
}
void
satip_satconf_destroy ( satip_frontend_t *lfe )
{
satip_satconf_t *sfc;
while ((sfc = TAILQ_FIRST(&lfe->sf_satconf)) != NULL)
satip_satconf_destroy0(sfc);
lfe->sf_positions = 0;
}
void
satip_satconf_save ( satip_frontend_t *lfe, htsmsg_t *m )
{
satip_satconf_t *sfc;
htsmsg_t *l, *e;
l = htsmsg_create_list();
TAILQ_FOREACH(sfc, &lfe->sf_satconf, sfc_link) {
e = htsmsg_create_map();
idnode_save(&sfc->sfc_id, e);
htsmsg_add_msg(l, NULL, e);
}
htsmsg_add_msg(m, "satconf", l);
}
/******************************************************************************
* Editor Configuration
*
* vim:sts=2:ts=2:sw=2:et
*****************************************************************************/

View file

@ -469,7 +469,7 @@ scanfile_find ( const char *id )
/* Type */
if (!(tok = strtok_r(tmp, "/", &s)))
return NULL;
goto fail;
if (!strcasecmp(tok, "dvbt"))
l = &scanfile_regions_DVBT;
else if (!strcasecmp(tok, "dvbc"))
@ -479,22 +479,27 @@ scanfile_find ( const char *id )
else if (!strcasecmp(tok, "atsc"))
l = &scanfile_regions_ATSC;
else
return NULL;
goto fail;
/* Region */
if (!(tok = strtok_r(NULL, "/", &s)))
return NULL;
goto fail;
LIST_FOREACH(r, l, sfr_link)
if (!strcmp(r->sfr_id, tok))
break;
if (!r) return NULL;
if (!r) goto fail;
/* Network */
if (!(tok = strtok_r(NULL, "/", &s)))
return NULL;
goto fail;
LIST_FOREACH(n, &r->sfr_networks, sfn_link)
if (!strcmp(n->sfn_id, tok))
break;
free(tmp);
return n;
fail:
free(tmp);
return NULL;
}

View file

@ -41,6 +41,7 @@
#include "tcp.h"
#include "access.h"
#include "http.h"
#include "upnp.h"
#include "webui/webui.h"
#include "epggrab.h"
#include "spawn.h"
@ -60,6 +61,7 @@
#include "timeshift.h"
#include "fsmonitor.h"
#include "lang_codes.h"
#include "esfilter.h"
#if ENABLE_LIBAV
#include "libav.h"
#include "plumbing/transcoding.h"
@ -68,6 +70,11 @@
#ifdef PLATFORM_LINUX
#include <sys/prctl.h>
#endif
#include <openssl/ssl.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/engine.h>
pthread_t main_tid;
@ -133,6 +140,9 @@ const tvh_caps_t tvheadend_capabilities[] = {
#if ENABLE_LINUXDVB
{ "linuxdvb", NULL },
#endif
#if ENABLE_SATIP_CLIENT
{ "satip_client", NULL },
#endif
#if ENABLE_LIBAV
{ "transcoding", &transcoding_enabled },
#endif
@ -467,6 +477,7 @@ main(int argc, char **argv)
#endif
*opt_bindaddr = NULL,
*opt_subscribe = NULL;
str_list_t opt_satip_xml = { .max = 10, .num = 0, .str = calloc(10, sizeof(char*)) };
str_list_t opt_tsfile = { .max = 10, .num = 0, .str = calloc(10, sizeof(char*)) };
cmdline_opt_t cmdline_opts[] = {
{ 0, NULL, "Generic Options", OPT_BOOL, NULL },
@ -488,6 +499,10 @@ main(int argc, char **argv)
#if ENABLE_LINUXDVB
{ 'a', "adapters", "Only use specified DVB adapters (comma separated)",
OPT_STR, &opt_dvb_adapters },
#endif
#if ENABLE_SATIP_CLIENT
{ 0, "satip_xml", "URL with the SAT>IP server XML location",
OPT_STR_LIST, &opt_satip_xml },
#endif
{ 0, NULL, "Server Connectivity", OPT_BOOL, NULL },
{ '6', "ipv6", "Listen on IPv6", OPT_BOOL, &opt_ipv6 },
@ -726,6 +741,11 @@ main(int argc, char **argv)
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, NULL);
trap_init(argv[0]);
/* SSL library init */
OPENSSL_config(NULL);
SSL_load_error_strings();
SSL_library_init();
/* Initialise configuration */
uuid_init();
@ -747,10 +767,13 @@ main(int argc, char **argv)
imagecache_init();
http_client_init();
esfilter_init();
service_init();
#if ENABLE_MPEGTS
mpegts_init(adapter_mask, &opt_tsfile, opt_tsfile_tuner);
mpegts_init(adapter_mask, &opt_satip_xml, &opt_tsfile, opt_tsfile_tuner);
#endif
channel_init();
@ -763,10 +786,12 @@ main(int argc, char **argv)
timeshift_init();
#endif
http_client_init();
tcp_server_init(opt_ipv6);
http_server_init(opt_bindaddr);
webui_init();
#if ENABLE_UPNP
upnp_server_init(opt_bindaddr);
#endif
service_mapper_init();
@ -813,14 +838,17 @@ main(int argc, char **argv)
mainloop();
#if ENABLE_UPNP
tvhftrace("main", upnp_server_done);
#endif
tvhftrace("main", htsp_done);
tvhftrace("main", http_server_done);
tvhftrace("main", webui_done);
tvhftrace("main", http_client_done);
tvhftrace("main", fsmonitor_done);
#if ENABLE_MPEGTS
tvhftrace("main", mpegts_done);
#endif
tvhftrace("main", http_client_done);
// Note: the locking is obviously a bit redundant, but without
// we need to disable the gtimer_arm call in epg_save()
@ -851,6 +879,8 @@ main(int argc, char **argv)
tvhftrace("main", hts_settings_done);
tvhftrace("main", dvb_done);
tvhftrace("main", lang_str_done);
tvhftrace("main", esfilter_done);
tvhftrace("main", urlparse_done);
tvhlog(LOG_NOTICE, "STOP", "Exiting HTS Tvheadend");
tvhlog_end();
@ -859,6 +889,23 @@ main(int argc, char **argv)
unlink(opt_pidpath);
free(opt_tsfile.str);
free(opt_satip_xml.str);
/* OpenSSL - welcome to the "cleanup" hell */
ENGINE_cleanup();
RAND_cleanup();
CRYPTO_cleanup_all_ex_data();
EVP_cleanup();
CONF_modules_free();
COMP_zlib_cleanup();
ERR_remove_state(0);
ERR_free_strings();
{
struct stack_st_SSL_COMP * pCOMP = SSL_COMP_get_compression_methods();
if (pCOMP)
sk_SSL_COMP_free(pCOMP);
}
/* end of OpenSSL cleanup code */
return 0;
}

199
src/rtsp.c Normal file
View file

@ -0,0 +1,199 @@
/*
* Tvheadend - RTSP routines
*
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <signal.h>
#include "tvheadend.h"
#include "htsbuf.h"
#include "tcp.h"
#include "http.h"
/*
* Utils
*/
int
rtsp_send( http_client_t *hc, http_cmd_t cmd,
const char *path, const char *query,
http_arg_list_t *hdr )
{
http_arg_list_t h;
size_t blen = 7 + strlen(hc->hc_host) + (path ? strlen(path) : 1) + 1;
char *buf = alloca(blen);
if (hc->hc_rtsp_session && cmd != RTSP_CMD_OPTIONS) {
if (hdr == NULL) {
hdr = &h;
http_arg_init(&h);
}
http_arg_set(hdr, "Session", hc->hc_rtsp_session);
}
snprintf(buf, blen, "rtsp://%s%s", hc->hc_host, path ? path : "/");
return http_client_send(hc, cmd, buf, query, hdr, NULL, 0);
}
void
rtsp_clear_session( http_client_t *hc )
{
free(hc->hc_rtsp_session);
free(hc->hc_rtp_dest);
hc->hc_rtp_port = 0;
hc->hc_rtpc_port = 0;
hc->hc_rtsp_session = NULL;
hc->hc_rtp_dest = NULL;
hc->hc_rtp_multicast = 0;
hc->hc_rtsp_stream_id = -1;
hc->hc_rtp_timeout = 60;
}
/*
* Options
*/
int
rtsp_options_decode( http_client_t *hc )
{
char *argv[32], *p;
int i, n, what = 0;
p = http_arg_get(&hc->hc_args, "Public");
n = http_tokenize(p, argv, 32, ',');
for (i = 1; i < n; i++) {
if (strcmp(argv[i], "DESCRIBE") == 0)
what |= 1;
else if (strcmp(argv[i], "SETUP") == 0)
what |= 2;
else if (strcmp(argv[i], "PLAY") == 0)
what |= 4;
else if (strcmp(argv[i], "TEARDOWN") == 0)
what |= 8;
}
return (hc->hc_code != 200 && what != 0x0f) ? -EIO : HTTP_CON_OK;
}
int
rtsp_setup_decode( http_client_t *hc, int satip )
{
char *argv[32], *argv2[2], *p;
int i, n, j;
#if 0
{ http_arg_t *ra;
TAILQ_FOREACH(ra, &hc->hc_args, link)
printf(" %s: %s\n", ra->key, ra->val); }
#endif
rtsp_clear_session(hc);
if (hc->hc_code != 200)
return -EIO;
p = http_arg_get(&hc->hc_args, "Session");
if (p == NULL)
return -EIO;
n = http_tokenize(p, argv, 32, ';');
if (n < 1)
return -EIO;
hc->hc_rtsp_session = strdup(argv[0]);
for (i = 1; i < n; i++) {
if (strncasecmp(argv[i], "timeout=", 8) == 0) {
hc->hc_rtp_timeout = atoi(argv[i] + 8);
if (hc->hc_rtp_timeout <= 20 || hc->hc_rtp_timeout > 3600)
return -EIO;
}
}
if (satip) {
p = http_arg_get(&hc->hc_args, "com.ses.streamID");
if (p == NULL)
return -EIO;
/* zero is valid stream id per specification */
while (*p && (*p == '0' || *p < ' '))
p++;
if (p[0] == '0' && p[1] == '\0') {
hc->hc_rtsp_stream_id = 0;
} else {
hc->hc_rtsp_stream_id = atoll(p);
if (hc->hc_rtsp_stream_id <= 0)
return -EIO;
}
}
p = http_arg_get(&hc->hc_args, "Transport");
if (p == NULL)
return -EIO;
n = http_tokenize(p, argv, 32, ';');
if (n < 3)
return -EIO;
if (strcasecmp(argv[0], "RTP/AVP"))
return -EIO;
hc->hc_rtp_multicast = strcasecmp(argv[1], "multicast") == 0;
if (strcasecmp(argv[1], "unicast") && !hc->hc_rtp_multicast)
return -EIO;
for (i = 2; i < n; i++) {
if (strncmp(argv[i], "destination=", 12) == 0)
hc->hc_rtp_dest = strdup(argv[i] + 12);
else if (strncmp(argv[i], "client_port=", 12) == 0) {
j = http_tokenize(argv[i] + 12, argv2, 2, '-');
if (j > 0) {
hc->hc_rtp_port = atoi(argv2[0]);
if (hc->hc_rtp_port <= 0)
return -EIO;
if (j > 1) {
hc->hc_rtpc_port = atoi(argv2[1]);
if (hc->hc_rtpc_port <= 0)
return -EIO;
}
} else {
return -EIO;
}
}
}
return HTTP_CON_OK;
}
int
rtsp_setup( http_client_t *hc,
const char *path, const char *query,
const char *multicast_addr,
int rtp_port, int rtpc_port )
{
http_arg_list_t h;
char transport[256];
if (multicast_addr) {
snprintf(transport, sizeof(transport),
"RTP/AVP;multicast;destination=%s;ttl=1;client_port=%i-%i",
multicast_addr, rtp_port, rtpc_port);
} else {
snprintf(transport, sizeof(transport),
"RTP/AVP;unicast;client_port=%i-%i", rtp_port, rtpc_port);
}
http_arg_init(&h);
http_arg_set(&h, "Transport", transport);
return rtsp_send(hc, RTSP_CMD_SETUP, path, query, &h);
}
int
rtsp_describe_decode( http_client_t *hc )
{
http_arg_t *ra;
/* TODO: Probably rewrite the data to the htsmsg tree ? */
printf("describe: %i\n", hc->hc_code);
TAILQ_FOREACH(ra, &hc->hc_args, link)
printf(" %s: %s\n", ra->key, ra->val);
printf("data:\n%s\n", hc->hc_data);
return HTTP_CON_OK;
}

View file

@ -44,6 +44,7 @@
#include "lang_codes.h"
#include "descrambler.h"
#include "input.h"
#include "esfilter.h"
static void service_data_timeout(void *aux);
static void service_class_save(struct idnode *self);
@ -284,7 +285,7 @@ service_stop(service_t *t)
/**
* Clean up each stream
*/
TAILQ_FOREACH(st, &t->s_components, es_link)
TAILQ_FOREACH(st, &t->s_filt_components, es_link)
stream_clean(st);
t->s_status = SERVICE_IDLE;
@ -319,6 +320,161 @@ service_remove_subscriber(service_t *t, th_subscription_t *s,
}
/**
*
*/
#define ESFM_USED (1<<0)
#define ESFM_IGNORE (1<<1)
static void
service_build_filter_add(service_t *t, elementary_stream_t *st,
elementary_stream_t **sta, int *p)
{
/* only once */
if (st->es_filter & ESFM_USED)
return;
st->es_filter |= ESFM_USED;
TAILQ_INSERT_TAIL(&t->s_filt_components, st, es_filt_link);
sta[*p] = st;
(*p)++;
}
/**
*
*/
void
service_build_filter(service_t *t)
{
elementary_stream_t *st, *st2, **sta;
esfilter_t *esf;
caid_t *ca;
int i, n, p, o, exclusive;
uint32_t mask;
/* rebuild the filtered and ordered components */
TAILQ_INIT(&t->s_filt_components);
for (i = ESF_CLASS_VIDEO; i <= ESF_CLASS_LAST; i++)
if (!TAILQ_EMPTY(&esfilters[i]))
goto filter;
TAILQ_FOREACH(st, &t->s_components, es_link)
TAILQ_INSERT_TAIL(&t->s_filt_components, st, es_filt_link);
return;
filter:
n = 0;
TAILQ_FOREACH(st, &t->s_components, es_link) {
st->es_filter = 0;
n++;
}
sta = alloca(sizeof(elementary_stream_t *) * n);
for (i = ESF_CLASS_VIDEO, p = 0; i <= ESF_CLASS_LAST; i++) {
o = p;
mask = esfilterclsmask[i];
if (TAILQ_EMPTY(&esfilters[i])) {
TAILQ_FOREACH(st, &t->s_components, es_link) {
if ((mask & SCT_MASK(st->es_type)) != 0)
service_build_filter_add(t, st, sta, &p);
}
continue;
}
exclusive = 0;
TAILQ_FOREACH(esf, &esfilters[i], esf_link) {
if (!esf->esf_enabled)
continue;
TAILQ_FOREACH(st, &t->s_components, es_link) {
if ((mask & SCT_MASK(st->es_type)) == 0)
continue;
if (esf->esf_type && (esf->esf_type & SCT_MASK(st->es_type)) == 0)
continue;
if (esf->esf_language[0] &&
strncmp(esf->esf_language, st->es_lang, 4))
continue;
if (esf->esf_service && esf->esf_service[0]) {
if (strcmp(esf->esf_service, idnode_uuid_as_str(&t->s_id)))
continue;
if (esf->esf_pid && esf->esf_pid != st->es_pid)
continue;
}
if (i == ESF_CLASS_CA &&
(esf->esf_caid != -1 || esf->esf_caprovider != -1)) {
LIST_FOREACH(ca, &st->es_caids, link) {
if (esf->esf_caid != -1 && ca->caid != esf->esf_caid)
continue;
if (esf->esf_caprovider != -1 && ca->providerid != esf->esf_caprovider)
continue;
break;
}
if (ca == NULL)
continue;
}
if (esf->esf_log)
tvhlog(LOG_INFO, "service", "esfilter: %s %03d %05d %s %s %s %s",
esfilter_class2txt(i), esf->esf_index,
st->es_pid, streaming_component_type2txt(st->es_type),
lang_code_get(st->es_lang), t->s_nicename,
esfilter_action2txt(esf->esf_action));
switch (esf->esf_action) {
case ESFA_NONE:
break;
case ESFA_IGNORE:
st->es_filter |= ESFM_IGNORE;
break;
case ESFA_USE:
service_build_filter_add(t, st, sta, &p);
break;
case ESFA_ONCE:
if (esf->esf_language[0] == '\0') {
service_build_filter_add(t, st, sta, &p);
} else {
int present = 0;
TAILQ_FOREACH(st2, &t->s_components, es_link) {
if ((st2->es_filter & ESFM_USED) == 0)
continue;
if (strcmp(st2->es_lang, st->es_lang) == 0) {
present = 1;
break;
}
}
if (!present)
service_build_filter_add(t, st, sta, &p);
}
break;
case ESFA_EXCLUSIVE:
break;
case ESFA_EMPTY:
if (p == o)
service_build_filter_add(t, st, sta, &p);
break;
default:
tvhlog(LOG_DEBUG, "service", "Unknown esfilter action %d", esf->esf_action);
break;
}
if (esf->esf_action == ESFA_EXCLUSIVE) {
/* forget previous work */
while (p > o) {
p--;
TAILQ_REMOVE(&t->s_filt_components, sta[p], es_filt_link);
}
service_build_filter_add(t, st, sta, &p);
exclusive = 1;
break;
}
}
}
if (!exclusive) {
TAILQ_FOREACH(st, &t->s_components, es_link) {
if ((mask & SCT_MASK(st->es_type)) != 0 &&
(st->es_filter & (ESFM_USED|ESFM_IGNORE)) == 0)
service_build_filter_add(t, st, sta, &p);
}
}
}
}
/**
*
*/
@ -336,6 +492,8 @@ service_start(service_t *t, int instance)
t->s_streaming_status = 0;
t->s_scrambled_seen = 0;
service_build_filter(t);
if((r = t->s_start_feed(t, instance)))
return r;
@ -349,7 +507,7 @@ service_start(service_t *t, int instance)
/**
* Initialize stream
*/
TAILQ_FOREACH(st, &t->s_components, es_link)
TAILQ_FOREACH(st, &t->s_filt_components, es_link)
stream_init(st);
pthread_mutex_unlock(&t->s_stream_mutex);
@ -501,6 +659,7 @@ service_destroy(service_t *t, int delconf)
t->s_status = SERVICE_ZOMBIE;
TAILQ_INIT(&t->s_filt_components);
while((st = TAILQ_FIRST(&t->s_components)) != NULL)
service_stream_destroy(t, st);
@ -552,6 +711,7 @@ service_create0
t->s_channel_name = service_channel_name;
t->s_provider_name = service_provider_name;
TAILQ_INIT(&t->s_components);
TAILQ_INIT(&t->s_filt_components);
t->s_last_pid = -1;
streaming_pad_init(&t->s_streaming_pad);
@ -631,7 +791,7 @@ elementary_stream_t *
service_stream_create(service_t *t, int pid,
streaming_component_type_t type)
{
elementary_stream_t *st;
elementary_stream_t *st, *st2;
int i = 0;
int idx = 0;
lock_assert(&t->s_stream_mutex);
@ -662,8 +822,14 @@ service_stream_create(service_t *t, int pid,
if(t->s_flags & S_DEBUG)
tvhlog(LOG_DEBUG, "service", "Add stream %s", st->es_nicename);
if(t->s_status == SERVICE_RUNNING)
stream_init(st);
if(t->s_status == SERVICE_RUNNING) {
service_build_filter(t);
TAILQ_FOREACH(st2, &t->s_filt_components, es_filt_link)
if (st2 == st) {
stream_init(st);
break;
}
}
return st;
}
@ -842,9 +1008,10 @@ service_restart(service_t *t, int had_components)
streaming_msg_free(sm);
}
service_build_filter(t);
descrambler_service_start(t);
if(TAILQ_FIRST(&t->s_components) != NULL) {
if(TAILQ_FIRST(&t->s_filt_components) != NULL) {
sm = streaming_msg_create_data(SMT_START,
service_build_stream_start(t));
streaming_pad_deliver(&t->s_streaming_pad, sm);
@ -871,7 +1038,7 @@ service_build_stream_start(service_t *t)
lock_assert(&t->s_stream_mutex);
TAILQ_FOREACH(st, &t->s_components, es_link)
TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link)
n++;
ss = calloc(1, sizeof(streaming_start_t) +
@ -880,7 +1047,7 @@ service_build_stream_start(service_t *t)
ss->ss_num_components = n;
n = 0;
TAILQ_FOREACH(st, &t->s_components, es_link) {
TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
streaming_start_component_t *ssc = &ss->ss_components[n++];
ssc->ssc_index = st->es_index;
ssc->ssc_type = st->es_type;

View file

@ -35,6 +35,7 @@ struct channel;
typedef struct elementary_stream {
TAILQ_ENTRY(elementary_stream) es_link;
TAILQ_ENTRY(elementary_stream) es_filt_link;
int es_position;
struct service *es_service;
@ -124,6 +125,9 @@ typedef struct elementary_stream {
/* SI section processing (horrible hack) */
void *es_section;
/* Filter temporary variable */
uint32_t es_filter;
} elementary_stream_t;
@ -400,9 +404,10 @@ typedef struct service {
int s_caid;
/**
* List of all components.
* List of all and filtered components.
*/
struct elementary_stream_queue s_components;
struct elementary_stream_queue s_filt_components;
int s_last_pid;
elementary_stream_t *s_last_es;
@ -429,6 +434,8 @@ void service_done(void);
int service_start(service_t *t, int instance);
void service_build_filter(service_t *t);
service_t *service_create0(service_t *t, const idclass_t *idc, const char *uuid, int source_type, htsmsg_t *conf);
#define service_create(t, c, u, s, m)\

View file

@ -74,7 +74,7 @@ subscription_link_service(th_subscription_t *s, service_t *t)
pthread_mutex_lock(&t->s_stream_mutex);
if(TAILQ_FIRST(&t->s_components) != NULL) {
if(TAILQ_FIRST(&t->s_filt_components) != NULL) {
if(s->ths_start_message != NULL)
streaming_msg_free(s->ths_start_message);
@ -122,7 +122,7 @@ subscription_unlink_service0(th_subscription_t *s, int reason, int stop)
streaming_target_disconnect(&t->s_streaming_pad, &s->ths_input);
if(stop &&
TAILQ_FIRST(&t->s_components) != NULL &&
TAILQ_FIRST(&t->s_filt_components) != NULL &&
s->ths_state == SUBSCRIPTION_GOT_SERVICE) {
// Send a STOP message to the subscription client
sm = streaming_msg_create_code(SMT_STOP, reason);

View file

@ -142,7 +142,9 @@ tcp_connect(const char *hostname, int port, char *errbuf, size_t errbufsize,
free(tmphstbuf);
if(r == -1) {
if(errno == EINPROGRESS) {
if(errno == EINPROGRESS && timeout < 0) {
err = 0;
} else if(errno == EINPROGRESS) {
struct pollfd pfd;
pfd.fd = fd;
@ -640,6 +642,9 @@ tcp_server_delete(void *server)
tcp_server_t *ts = server;
tvhpoll_event_t ev;
if (server == NULL)
return;
memset(&ev, 0, sizeof(ev));
ev.fd = ts->serverfd;
ev.events = TVHPOLL_IN;

View file

@ -220,8 +220,11 @@ typedef enum {
SCT_MP4A,
SCT_VP8,
SCT_VORBIS,
SCT_LAST = SCT_VORBIS
} streaming_component_type_t;
#define SCT_MASK(t) (1 << (t))
#define SCT_ISVIDEO(t) ((t) == SCT_MPEG2VIDEO || (t) == SCT_H264 || \
(t) == SCT_VP8)
@ -575,6 +578,11 @@ uint32_t tvh_crc32(const uint8_t *data, size_t datalen, uint32_t crc);
int base64_decode(uint8_t *out, const char *in, int out_size);
char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size);
/* Calculate the output size needed to base64-encode x bytes. */
#define BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1)
int put_utf8(char *out, int c);
static inline int64_t ts_rescale(int64_t ts, int tb)

View file

@ -270,7 +270,7 @@ void tvhlogv ( const char *file, int line,
pthread_mutex_lock(&tvhlog_mutex);
/* Check for full */
if (tvhlog_queue_full) {
if (tvhlog_queue_full || !tvhlog_run) {
pthread_mutex_unlock(&tvhlog_mutex);
return;
}

View file

@ -91,6 +91,8 @@ tvhpoll_create ( size_t n )
void tvhpoll_destroy ( tvhpoll_t *tp )
{
if (tp == NULL)
return;
#if ENABLE_EPOLL || ENABLE_KQUEUE
free(tp->ev);
close(tp->fd);

485
src/udp.c Normal file
View file

@ -0,0 +1,485 @@
/*
* TVHeadend - UDP common routines
*
* Copyright (C) 2013 Adam Sutton
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include "tvheadend.h"
#include "udp.h"
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <assert.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#if defined(PLATFORM_LINUX)
#include <linux/netdevice.h>
#elif defined(PLATFORM_FREEBSD)
# include <net/if.h>
# ifndef IPV6_ADD_MEMBERSHIP
# define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
# define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
# endif
#endif
extern int tcp_preferred_address_family;
static int
udp_resolve( udp_connection_t *uc, int receiver )
{
struct addrinfo hints, *res, *ressave, *use = NULL;
char port_buf[6];
int x;
snprintf(port_buf, 6, "%d", uc->port);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = receiver ? AI_PASSIVE : 0;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
x = getaddrinfo(uc->host, port_buf, &hints, &res);
if (x < 0) {
tvhlog(LOG_ERR, uc->subsystem, "getaddrinfo: %s: %s",
uc->host != NULL ? uc->host : "*",
x == EAI_SYSTEM ? strerror(errno) : gai_strerror(x));
return -1;
}
ressave = res;
while (res) {
if (res->ai_family == tcp_preferred_address_family) {
use = res;
break;
} else if (use == NULL) {
use = res;
}
res = res->ai_next;
}
if (use->ai_family == AF_INET6) {
uc->ip.ss_family = AF_INET6;
IP_AS_V6(uc->ip, port) = htons(uc->port);
memcpy(&IP_AS_V6(uc->ip, addr), &((struct sockaddr_in6 *)use->ai_addr)->sin6_addr,
sizeof(struct in6_addr));
uc->multicast = !!IN6_IS_ADDR_MULTICAST(&IP_AS_V6(uc->ip, addr));
} else if (use->ai_family == AF_INET) {
uc->ip.ss_family = AF_INET;
IP_AS_V4(uc->ip, port) = htons(uc->port);
IP_AS_V4(uc->ip, addr) = ((struct sockaddr_in *)use->ai_addr)->sin_addr;
uc->multicast = !!IN_MULTICAST(ntohl(IP_AS_V4(uc->ip, addr.s_addr)));
}
freeaddrinfo(ressave);
if (uc->ip.ss_family != AF_INET && uc->ip.ss_family != AF_INET6) {
tvherror(uc->subsystem, "%s - failed to process host '%s'", uc->name, uc->host);
return -1;
}
return 0;
}
udp_connection_t *
udp_bind ( const char *subsystem, const char *name,
const char *bindaddr, int port,
const char *ifname, int rxsize )
{
int fd, solip, reuse = 1;
struct ifreq ifr;
udp_connection_t *uc;
char buf[256];
socklen_t addrlen;
uc = calloc(1, sizeof(udp_connection_t));
uc->fd = -1;
uc->host = bindaddr ? strdup(bindaddr) : NULL;
uc->port = port;
uc->ifname = ifname ? strdup(ifname) : NULL;
uc->subsystem = subsystem ? strdup(subsystem) : NULL;
uc->name = name ? strdup(name) : NULL;
uc->rxtxsize = rxsize;
if (udp_resolve(uc, 1) < 0) {
udp_close(uc);
return UDP_FATAL_ERROR;
}
/* Open socket */
if ((fd = tvh_socket(uc->ip.ss_family, SOCK_DGRAM, 0)) == -1) {
tvherror(subsystem, "%s - failed to create socket [%s]",
name, strerror(errno));
udp_close(uc);
return UDP_FATAL_ERROR;
}
/* Mark reuse address */
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
/* Bind to interface */
memset(&ifr, 0, sizeof(ifr));
if (ifname && *ifname) {
snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
if (ioctl(fd, SIOCGIFINDEX, &ifr)) {
tvherror(subsystem, "%s - could not find interface %s",
name, ifname);
goto error;
}
}
/* IPv4 */
if (uc->ip.ss_family == AF_INET) {
struct ip_mreqn m;
memset(&m, 0, sizeof(m));
/* Bind */
if (bind(fd, (struct sockaddr *)&uc->ip, sizeof(struct sockaddr_in)) == -1) {
inet_ntop(AF_INET, &IP_AS_V4(uc->ip, addr), buf, sizeof(buf));
tvherror(subsystem, "%s - cannot bind %s:%hu [e=%s]",
name, buf, ntohs(IP_AS_V4(uc->ip, port)), strerror(errno));
goto error;
}
if (uc->multicast) {
/* Join group */
m.imr_multiaddr = IP_AS_V4(uc->ip, addr);
m.imr_address.s_addr = 0;
#if defined(PLATFORM_LINUX)
m.imr_ifindex = ifr.ifr_ifindex;
#elif defined(PLATFORM_FREEBSD)
m.imr_ifindex = ifr.ifr_index;
#endif
#ifdef SOL_IP
solip = SOL_IP;
#else
{
struct protoent *pent;
pent = getprotobyname("ip");
solip = (pent != NULL) ? pent->p_proto : 0;
}
#endif
if (setsockopt(fd, solip, IP_ADD_MEMBERSHIP, &m, sizeof(m))) {
inet_ntop(AF_INET, &m.imr_multiaddr, buf, sizeof(buf));
tvhwarn("iptv", "%s - cannot join %s [%s]",
name, buf, strerror(errno));
}
}
/* Bind to IPv6 group */
} else {
struct ipv6_mreq m;
memset(&m, 0, sizeof(m));
/* Bind */
if (bind(fd, (struct sockaddr *)&uc->ip, sizeof(struct sockaddr_in6)) == -1) {
inet_ntop(AF_INET6, &IP_AS_V6(uc->ip, addr), buf, sizeof(buf));
tvherror(subsystem, "%s - cannot bind %s:%hu [e=%s]",
name, buf, ntohs(IP_AS_V6(uc->ip, port)), strerror(errno));
goto error;
}
if (uc->multicast) {
/* Join group */
m.ipv6mr_multiaddr = IP_AS_V6(uc->ip, addr);
#if defined(PLATFORM_LINUX)
m.ipv6mr_interface = ifr.ifr_ifindex;
#elif defined(PLATFORM_FREEBSD)
m.ipv6mr_interface = ifr.ifr_index;
#endif
#ifdef SOL_IPV6
if (setsockopt(fd, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &m, sizeof(m))) {
inet_ntop(AF_INET, &m.ipv6mr_multiaddr, buf, sizeof(buf));
tvhwarn(subsystem, "%s - cannot join %s [%s]",
name, buf, strerror(errno));
}
#else
tvherror("iptv", "IPv6 multicast not supported");
goto error;
#endif
}
}
addrlen = sizeof(uc->ip);
getsockname(fd, (struct sockaddr *)&uc->ip, &addrlen);
/* Increase RX buffer size */
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rxsize, sizeof(rxsize)) == -1)
tvhwarn("iptv", "%s - cannot increase UDP rx buffer size [%s]",
name, strerror(errno));
uc->fd = fd;
return uc;
error:
udp_close(uc);
return NULL;
}
int
udp_bind_double ( udp_connection_t **_u1, udp_connection_t **_u2,
const char *subsystem, const char *name1,
const char *name2, const char *host, int port,
const char *ifname, int rxsize1, int rxsize2 )
{
udp_connection_t *u1 = NULL, *u2 = NULL;
udp_connection_t *ucs[10] = { NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL };
int pos = 0, i, port2;
memset(&ucs, 0, sizeof(ucs));
while (1) {
u1 = udp_bind(subsystem, name1, host, port, ifname, rxsize1);
if (u1 == NULL || u1 == UDP_FATAL_ERROR)
goto fail;
port2 = ntohs(IP_PORT(u1->ip));
/* RTP port should be even, RTCP port should be odd */
if ((port2 % 2) == 0) {
u2 = udp_bind(subsystem, name2, host, port2 + 1, ifname, rxsize2);
if (u2 != NULL && u2 != UDP_FATAL_ERROR)
break;
}
ucs[pos++] = u1;
if (port || pos >= ARRAY_SIZE(ucs))
goto fail;
}
for (i = 0; i < pos; i++)
udp_close(ucs[i]);
*_u1 = u1;
*_u2 = u2;
return 0;
fail:
for (i = 0; i < pos; i++)
udp_close(ucs[i]);
return -1;
}
udp_connection_t *
udp_connect ( const char *subsystem, const char *name,
const char *host, int port,
const char *ifname, int txsize )
{
int fd;
struct ifreq ifr;
udp_connection_t *uc;
uc = calloc(1, sizeof(udp_connection_t));
uc->fd = -1;
uc->host = host ? strdup(host) : NULL;
uc->port = port;
uc->ifname = ifname ? strdup(ifname) : NULL;
uc->subsystem = subsystem ? strdup(subsystem) : NULL;
uc->name = name ? strdup(name) : NULL;
uc->rxtxsize = txsize;
if (udp_resolve(uc, 1) < 0) {
udp_close(uc);
return UDP_FATAL_ERROR;
}
/* Open socket */
if ((fd = tvh_socket(uc->ip.ss_family, SOCK_DGRAM, 0)) == -1) {
tvherror(subsystem, "%s - failed to create socket [%s]",
name, strerror(errno));
udp_close(uc);
return UDP_FATAL_ERROR;
}
/* Bind to interface */
memset(&ifr, 0, sizeof(ifr));
if (ifname && *ifname) {
snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
if (ioctl(fd, SIOCGIFINDEX, &ifr)) {
tvherror(subsystem, "%s - could not find interface %s",
name, ifname);
goto error;
}
}
/* Increase TX buffer size */
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &txsize, sizeof(txsize)) == -1)
tvhwarn("iptv", "%s - cannot increase UDP tx buffer size [%s]",
name, strerror(errno));
uc->fd = fd;
return uc;
error:
udp_close(uc);
return NULL;
}
void
udp_close( udp_connection_t *uc )
{
if (uc == NULL || uc == UDP_FATAL_ERROR)
return;
if (uc->fd >= 0)
close(uc->fd);
free(uc->host);
free(uc->ifname);
free(uc->subsystem);
free(uc->name);
free(uc);
}
int
udp_write( udp_connection_t *uc, const void *buf, size_t len,
struct sockaddr_storage *storage )
{
int r;
if (storage == NULL)
storage = &uc->ip;
while (len) {
r = sendto(uc->fd, buf, len, 0, (struct sockaddr*)storage,
storage->ss_family == AF_INET6 ?
sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
if (r < 0) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
usleep(100);
continue;
}
break;
}
len -= r;
buf += r;
}
return len;
}
int
udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q,
struct sockaddr_storage *storage )
{
htsbuf_data_t *hd;
int l, r = 0;
void *p;
while ((hd = TAILQ_FIRST(&q->hq_q)) != NULL) {
if (!r) {
l = hd->hd_data_len - hd->hd_data_off;
p = hd->hd_data + hd->hd_data_off;
r = udp_write(uc, p, l, storage);
}
htsbuf_data_free(q, hd);
}
q->hq_size = 0;
return r;
}
/*
* UDP multi packet receive support
*/
#if !defined (CONFIG_RECVMMSG) && defined(__linux__)
/* define the syscall - works only for linux */
#include <linux/unistd.h>
#ifdef __NR_recvmmsg
struct mmsghdr {
struct msghdr msg_hdr;
unsigned int msg_len;
};
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
unsigned int flags, struct timespec *timeout);
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
unsigned int flags, struct timespec *timeout)
{
return syscall(__NR_recvmmsg, sockfd, msgvec, vlen, flags, timeout);
}
#define CONFIG_RECVMMSG
#endif
#endif
#ifndef CONFIG_RECVMMSG
struct mmsghdr {
struct msghdr msg_hdr;
unsigned int msg_len;
};
static int
recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
unsigned int flags, struct timespec *timeout)
{
ssize_t r;
unsigned int i;
for (i = 0; i < vlen; i++) {
r = recvmsg(sockfd, &msgvec->msg_hdr, flags);
if (r < 0)
return (i > 0) ? i : r;
msgvec->msg_len = r;
msgvec++;
}
return i;
}
#endif
void
udp_multirecv_init( udp_multirecv_t *um, int packets, int psize )
{
int i;
um->um_psize = psize;
um->um_packets = packets;
um->um_data = malloc(packets * psize);
um->um_iovec = malloc(packets * sizeof(struct iovec));
um->um_riovec = malloc(packets * sizeof(struct iovec));
um->um_msg = calloc(packets, sizeof(struct mmsghdr));
for (i = 0; i < packets; i++) {
((struct mmsghdr *)um->um_msg)[i].msg_hdr.msg_iov = &um->um_iovec[i];
((struct mmsghdr *)um->um_msg)[i].msg_hdr.msg_iovlen = 1;
um->um_iovec[i].iov_base = /* follow thru */
um->um_riovec[i].iov_base = um->um_data + i * psize;
um->um_iovec[i].iov_len = psize;
}
}
void
udp_multirecv_free( udp_multirecv_t *um )
{
free(um->um_msg); um->um_msg = NULL;
free(um->um_iovec); um->um_iovec = NULL;
free(um->um_data); um->um_data = NULL;
um->um_psize = 0;
um->um_packets = 0;
}
int
udp_multirecv_read( udp_multirecv_t *um, int fd, int packets,
struct iovec **iovec )
{
int n, i;
if (packets > um->um_packets)
packets = um->um_packets;
n = recvmmsg(fd, (struct mmsghdr *)um->um_msg, packets, MSG_DONTWAIT, NULL);
if (n > 0) {
for (i = 0; i < n; i++)
um->um_riovec[i].iov_len = ((struct mmsghdr *)um->um_msg)[i].msg_len;
*iovec = um->um_riovec;
}
return n;
}

91
src/udp.h Normal file
View file

@ -0,0 +1,91 @@
/*
* tvheadend, UDP interface
* Copyright (C) 2013 Adam Sutton
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UDP_H_
#define UDP_H_
#include <netinet/in.h>
#include "tcp.h"
#define UDP_FATAL_ERROR ((void *)-1)
#define IP_AS_V4(storage, f) ((struct sockaddr_in *)&(storage))->sin_##f
#define IP_AS_V6(storage, f) ((struct sockaddr_in6 *)&(storage))->sin6_##f
#define IP_IN_ADDR(storage) \
((storage).ss_family == AF_INET6 ? \
&((struct sockaddr_in6 *)&(storage))->sin6_addr : \
(void *)&((struct sockaddr_in *)&(storage))->sin_addr)
#define IP_PORT(storage) \
((storage).ss_family == AF_INET6 ? \
((struct sockaddr_in6 *)&(storage))->sin6_port : \
((struct sockaddr_in *)&(storage))->sin_port)
typedef struct udp_connection {
char *host;
int port;
int multicast;
char *ifname;
struct sockaddr_storage ip;
int fd;
char *subsystem;
char *name;
int rxtxsize;
} udp_connection_t;
udp_connection_t *
udp_bind ( const char *subsystem, const char *name,
const char *bindaddr, int port,
const char *ifname, int rxsize );
int
udp_bind_double ( udp_connection_t **_u1, udp_connection_t **_u2,
const char *subsystem, const char *name1,
const char *name2, const char *host, int port,
const char *ifname, int rxsize1, int rxsize2 );
udp_connection_t *
udp_connect ( const char *subsystem, const char *name,
const char *host, int port,
const char *ifname, int txsize );
void
udp_close ( udp_connection_t *uc );
int
udp_write( udp_connection_t *uc, const void *buf, size_t len,
struct sockaddr_storage *storage );
int
udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q,
struct sockaddr_storage *storage );
typedef struct udp_multirecv {
int um_psize;
int um_packets;
uint8_t *um_data;
struct iovec *um_iovec;
struct iovec *um_riovec;
struct mmsghdr *um_msg;
} udp_multirecv_t;
void
udp_multirecv_init( udp_multirecv_t *um, int packets, int psize );
void
udp_multirecv_free( udp_multirecv_t *um );
int
udp_multirecv_read( udp_multirecv_t *um, int fd, int packets,
struct iovec **iovec );
#endif /* UDP_H_ */

219
src/upnp.c Normal file
View file

@ -0,0 +1,219 @@
/*
* tvheadend, UPnP interface
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include "tvheadend.h"
#include "tvhpoll.h"
#include "upnp.h"
int upnp_running;
static pthread_t upnp_tid;
pthread_mutex_t upnp_lock;
TAILQ_HEAD(upnp_active_services, upnp_service);
typedef struct upnp_data {
TAILQ_ENTRY(upnp_data) data_link;
struct sockaddr_storage storage;
htsbuf_queue_t queue;
} upnp_data_t;
TAILQ_HEAD(upnp_data_queue_write, upnp_data);
static struct upnp_active_services upnp_services;
static struct upnp_data_queue_write upnp_data_write;
static struct sockaddr_storage upnp_ipv4_multicast;
/*
*
*/
upnp_service_t *upnp_service_create0( upnp_service_t *us )
{
pthread_mutex_lock(&upnp_lock);
TAILQ_INSERT_TAIL(&upnp_services, us, us_link);
pthread_mutex_unlock(&upnp_lock);
return us;
}
void upnp_service_destroy( upnp_service_t *us )
{
pthread_mutex_lock(&upnp_lock);
TAILQ_REMOVE(&upnp_services, us, us_link);
us->us_destroy(us);
pthread_mutex_unlock(&upnp_lock);
free(us);
}
/*
*
*/
void
upnp_send( htsbuf_queue_t *q, struct sockaddr_storage *storage )
{
upnp_data_t *data;
if (!upnp_running)
return;
data = calloc(1, sizeof(upnp_data_t));
htsbuf_queue_init(&data->queue, 0);
htsbuf_appendq(&data->queue, q);
if (storage == NULL)
data->storage = upnp_ipv4_multicast;
else
data->storage = *storage;
pthread_mutex_lock(&upnp_lock);
TAILQ_INSERT_TAIL(&upnp_data_write, data, data_link);
pthread_mutex_unlock(&upnp_lock);
}
/*
* Discovery thread
*/
static void *
upnp_thread( void *aux )
{
char *bindaddr = aux;
tvhpoll_t *poll = tvhpoll_create(2);
tvhpoll_event_t ev[2];
upnp_data_t *data;
udp_connection_t *multicast = NULL, *unicast = NULL;
udp_connection_t *conn;
unsigned char buf[16384];
upnp_service_t *us;
struct sockaddr_storage ip;
socklen_t iplen;
size_t size;
int r;
multicast = udp_bind("upnp", "upnp_thread_multicast",
"239.255.255.250", 1900,
NULL, 32*1024);
if (multicast == NULL || multicast == UDP_FATAL_ERROR)
goto error;
unicast = udp_bind("upnp", "upnp_thread_unicast", bindaddr, 1900,
NULL, 32*1024);
if (unicast == NULL || unicast == UDP_FATAL_ERROR)
goto error;
memset(&ev, 0, sizeof(ev));
ev[0].fd = multicast->fd;
ev[0].events = TVHPOLL_IN;
ev[0].data.ptr = multicast;
ev[1].fd = unicast->fd;
ev[1].events = TVHPOLL_IN;
ev[1].data.ptr = unicast;
tvhpoll_add(poll, ev, 2);
while (upnp_running && multicast->fd >= 0) {
r = tvhpoll_wait(poll, ev, 2, 1000);
while (r-- > 0) {
if ((ev[r].events & TVHPOLL_IN) != 0) {
conn = ev[r].data.ptr;
iplen = sizeof(ip);
size = recvfrom(conn->fd, buf, sizeof(buf), 0,
(struct sockaddr *)&ip, &iplen);
#if ENABLE_TRACE
if (size > 0) {
char tbuf[256];
inet_ntop(ip.ss_family, IP_IN_ADDR(ip), tbuf, sizeof(tbuf));
tvhtrace("upnp", "%s - received data from %s:%hu [size=%zi]",
conn == multicast ? "multicast" : "unicast",
tbuf, IP_PORT(ip), size);
tvhlog_hexdump("upnp", buf, size);
}
#endif
/* TODO: a filter */
TAILQ_FOREACH(us, &upnp_services, us_link)
us->us_received(buf, size, conn, &ip);
}
}
while (1) {
pthread_mutex_lock(&upnp_lock);
data = TAILQ_FIRST(&upnp_data_write);
if (data)
TAILQ_REMOVE(&upnp_data_write, data, data_link);
pthread_mutex_unlock(&upnp_lock);
if (data == NULL)
break;
udp_write_queue(unicast, &data->queue, &data->storage);
htsbuf_queue_flush(&data->queue);
free(data);
}
}
error:
upnp_running = 0;
tvhpoll_destroy(poll);
udp_close(unicast);
udp_close(multicast);
return NULL;
}
/*
* Fire up UPnP server
*/
void
upnp_server_init(const char *bindaddr)
{
int r;
memset(&upnp_ipv4_multicast, 0, sizeof(upnp_ipv4_multicast));
upnp_ipv4_multicast.ss_family = AF_INET;
IP_AS_V4(upnp_ipv4_multicast, port) = htons(1900);
r = inet_pton(AF_INET, "239.255.255.250", &IP_AS_V4(upnp_ipv4_multicast, addr));
assert(r);
pthread_mutex_init(&upnp_lock, NULL);
TAILQ_INIT(&upnp_data_write);
TAILQ_INIT(&upnp_services);
upnp_running = 1;
tvhthread_create(&upnp_tid, NULL, upnp_thread, (char *)bindaddr, 0);
}
void
upnp_server_done(void)
{
upnp_data_t *data;
upnp_service_t *us;
upnp_running = 0;
pthread_kill(upnp_tid, SIGTERM);
pthread_join(upnp_tid, NULL);
while ((us = TAILQ_FIRST(&upnp_services)) != NULL)
upnp_service_destroy(us);
while ((data = TAILQ_FIRST(&upnp_data_write)) != NULL) {
TAILQ_REMOVE(&upnp_data_write, data, data_link);
htsbuf_queue_flush(&data->queue);
free(data);
}
}

47
src/upnp.h Normal file
View file

@ -0,0 +1,47 @@
/*
* tvheadend, UPnP interface
* Copyright (C) 2014 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UPNP_H_
#define UPNP_H_
#include "http.h"
#include "udp.h"
extern int upnp_running;
typedef struct upnp_service upnp_service_t;
struct upnp_service {
TAILQ_ENTRY(upnp_service) us_link;
void (*us_received)(uint8_t *data, size_t len,
udp_connection_t *conn,
struct sockaddr_storage *storage);
void (*us_destroy)(upnp_service_t *us);
};
upnp_service_t *upnp_service_create0(upnp_service_t *us);
#define upnp_service_create(us) \
upnp_service_create0(calloc(1, sizeof(struct us)))
void upnp_service_destroy(upnp_service_t *service);
void upnp_send(htsbuf_queue_t *q, struct sockaddr_storage *storage);
void upnp_server_init(const char *bindaddr);
void upnp_server_done(void);
#endif /* UPNP_H_ */

107
src/url.c
View file

@ -25,11 +25,40 @@
#include <regex.h>
#include <string.h>
void
urlreset ( url_t *url )
{
free(url->scheme);
free(url->user);
free(url->pass);
free(url->host);
free(url->path);
free(url->query);
free(url->frag);
free(url->raw);
memset(url, 0, sizeof(*url));
}
void
urlcopy ( url_t *dst, const url_t *src )
{
dst->scheme = strdup(src->scheme);
dst->user = strdup(src->user);
dst->pass = strdup(src->pass);
dst->host = strdup(src->host);
dst->port = src->port;
dst->path = strdup(src->path);
dst->query = strdup(src->query);
dst->frag = strdup(src->frag);
dst->raw = strdup(src->raw);
}
/* Use liburiparser if available */
#if ENABLE_URIPARSER
#include <uriparser/Uri.h>
int
int
urlparse ( const char *str, url_t *url )
{
UriParserStateA state;
@ -37,6 +66,11 @@ urlparse ( const char *str, url_t *url )
UriUriA uri;
char *s, buf[256];
if (str == NULL || url == NULL)
return -1;
urlreset(url);
/* Parse */
state.uri = &uri;
if (uriParseUriA(&state, str) != URI_SUCCESS) {
@ -45,44 +79,52 @@ urlparse ( const char *str, url_t *url )
}
/* Store raw */
strncpy(url->raw, str, sizeof(url->raw));
url->raw = strdup(str);
/* Copy */
#define uri_copy(y, x)\
if (x.first) {\
size_t len = x.afterLast - x.first;\
y = strndup(x.first, len);\
}
#define uri_copy_static(y, x)\
if (x.first) {\
size_t len = x.afterLast - x.first;\
strncpy(y, x.first, len);\
y[len] = 0;\
y[len] = '\0';\
} else {\
*y = 0;\
y[0] = '\0';\
}
uri_copy(url->scheme, uri.scheme);
uri_copy(url->host, uri.hostText);
uri_copy(url->user, uri.userInfo);
uri_copy(url->query, uri.query);
uri_copy(url->frag, uri.fragment);
uri_copy(buf, uri.portText);
uri_copy_static(buf, uri.portText);
if (*buf)
url->port = atoi(buf);
else
url->port = 0;
*url->path = 0;
path = uri.pathHead;
while (path) {
uri_copy_static(buf, path->text);
if (url->path)
url->path = realloc(url->path, strlen(url->path) + strlen(buf) + 2);
else
url->path = calloc(1, strlen(buf) + 2);
strcat(url->path, "/");
uri_copy(buf, path->text);
strcat(url->path, buf);
path = path->next;
}
// TODO: query/fragment
/* Split user/pass */
s = strstr(url->user, ":");
if (s) {
strcpy(url->pass, s+1);
*s = 0;
} else {
*url->pass = 0;
if (url->user) {
s = strstr(url->user, ":");
if (s) {
strcpy(url->pass, s+1);
*s = 0;
}
}
/* Cleanup */
@ -90,6 +132,11 @@ urlparse ( const char *str, url_t *url )
return 0;
}
void
urlparse_done( void )
{
}
/* Fallback to limited support */
#else /* ENABLE_URIPARSER */
@ -100,29 +147,38 @@ urlparse ( const char *str, url_t *url )
#define HC "[a-z0-9\\-\\.]"
#define URL_RE "^([A-Za-z]+)://(("UC"+)(:("PC"+))?@)?("HC"+)(:([0-9]+))?(/[^\\?]*)?(.([^#]*))?(#(.*))?"
static regex_t *urlparse_exp = NULL;
int
urlparse ( const char *str, url_t *url )
{
static regex_t *exp = NULL;
regmatch_t m[16];
char buf[16];
if (str == NULL || url == NULL)
return -1;
urlreset(url);
/* Create regexp */
if (!exp) {
exp = calloc(1, sizeof(regex_t));
if (regcomp(exp, URL_RE, REG_ICASE | REG_EXTENDED)) {
if (!urlparse_exp) {
urlparse_exp = calloc(1, sizeof(regex_t));
if (regcomp(urlparse_exp, URL_RE, REG_ICASE | REG_EXTENDED)) {
tvherror("url", "failed to compile regexp");
exit(1);
}
}
/* Execute */
if (regexec(exp, str, ARRAY_SIZE(m), m, 0))
return 1;
if (regexec(urlparse_exp, str, ARRAY_SIZE(m), m, 0))
return -1;
/* Extract data */
#define copy(x, i)\
{\
x = strndup(str+m[i].rm_so, m[i].rm_eo - m[i].rm_so);\
}(void)0
#define copy_static(x, i)\
{\
int len = m[i].rm_eo - m[i].rm_so;\
if (len >= sizeof(x) - 1)\
@ -135,14 +191,23 @@ urlparse ( const char *str, url_t *url )
copy(url->pass, 5);
copy(url->host, 6);
copy(url->path, 9);
copy(buf, 8);
copy_static(buf, 8);
url->port = atoi(buf);
copy(url->query, 11);
copy(url->frag, 13);
strncpy(url->raw, str, sizeof(url->raw));
url->raw = strdup(str);
return 0;
}
void
urlparse_done( void )
{
if (urlparse_exp) {
regfree(urlparse_exp);
free(urlparse_exp);
}
}
#endif /* ENABLE_URIPARSER */

View file

@ -22,23 +22,23 @@
#include <stdint.h>
// TODO: limits are a bit arbitrary and it's a bit inflexible, but it
// does keep things simple, not having dynamically allocated strings
/* URL structure */
typedef struct url
{
char scheme[32];
char user[128];
char pass[128];
char host[256];
short port;
char path[256];
char query[1024];
char frag[256];
char raw[2048];
char *scheme;
char *user;
char *pass;
char *host;
short port;
char *path;
char *query;
char *frag;
char *raw;
} url_t;
void urlreset ( url_t *url );
int urlparse ( const char *str, url_t *url );
void urlparse_done ( void );
void urlcopy ( url_t *dst, const url_t *src );
#endif

View file

@ -193,6 +193,41 @@ base64_decode(uint8_t *out, const char *in, int out_size)
return dst - out;
}
/*
* b64_encode: Stolen from VLC's http.c.
* Simplified by Michael.
* Fixed edge cases and made it work from data (vs. strings) by Ryan.
*/
char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size)
{
static const char b64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char *ret, *dst;
unsigned i_bits = 0;
int i_shift = 0;
int bytes_remaining = in_size;
if (in_size >= UINT_MAX / 4 ||
out_size < BASE64_SIZE(in_size))
return NULL;
ret = dst = out;
while (bytes_remaining) {
i_bits = (i_bits << 8) + *in++;
bytes_remaining--;
i_shift += 8;
do {
*dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f];
i_shift -= 6;
} while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0));
}
while ((dst - ret) & 3)
*dst++ = '=';
*dst = '\0';
return ret;
}
/**
*

View file

@ -149,6 +149,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
extjs_load(hq, "static/app/capmteditor.js");
extjs_load(hq, "static/app/tvadapters.js");
extjs_load(hq, "static/app/idnode.js");
extjs_load(hq, "static/app/esfilter.js");
#if ENABLE_LINUXDVB
extjs_load(hq, "static/app/mpegts.js");
#endif

View file

@ -0,0 +1,108 @@
/*
* Elementary Stream Filters
*/
tvheadend.esfilter_tab = function(panel)
{
tvheadend.idnode_grid(panel, {
url : 'api/esfilter/video',
comet : 'esfilter_video',
titleS : 'Video Stream Filter',
titleP : 'Video Stream Filters',
tabIndex : 0,
add : {
url : 'api/esfilter/video',
create : {}
},
del : true,
move : true,
help : function() {
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
}
});
tvheadend.idnode_grid(panel, {
url : 'api/esfilter/audio',
comet : 'esfilter_audio',
titleS : 'Audio Stream Filter',
titleP : 'Audio Stream Filters',
tabIndex : 1,
add : {
url : 'api/esfilter/audio',
create : {}
},
del : true,
move : true,
help : function() {
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
}
});
tvheadend.idnode_grid(panel, {
url : 'api/esfilter/teletext',
comet : 'esfilter_teletext',
titleS : 'Teletext Stream Filter',
titleP : 'Teletext Stream Filters',
tabIndex : 2,
add : {
url : 'api/esfilter/teletext',
create : {}
},
del : true,
move : true,
help : function() {
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
}
});
tvheadend.idnode_grid(panel, {
url : 'api/esfilter/subtit',
comet : 'esfilter_subtit',
titleS : 'Subtitle Stream Filter',
titleP : 'Subtitle Stream Filters',
tabIndex : 3,
add : {
url : 'api/esfilter/subtit',
create : {}
},
del : true,
move : true,
help : function() {
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
}
});
tvheadend.idnode_grid(panel, {
url : 'api/esfilter/ca',
comet : 'esfilter_ca',
titleS : 'CA Stream Filter',
titleP : 'CA Stream Filters',
tabIndex : 4,
add : {
url : 'api/esfilter/ca',
create : {}
},
del : true,
move : true,
help : function() {
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
}
});
tvheadend.idnode_grid(panel, {
url : 'api/esfilter/other',
comet : 'esfilter_other',
titleS : 'Other Stream Filter',
titleP : 'Other Stream Filters',
tabIndex : 5,
add : {
url : 'api/esfilter/other',
create : {}
},
del : true,
move : true,
help : function() {
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
}
});
}

View file

@ -148,6 +148,14 @@
background-image: url(../icons/delete.png) !important;
}
.moveup {
background-image: url(../icons/arrow_up.png) !important;
}
.movedown {
background-image: url(../icons/arrow_down.png) !important;
}
.save {
background-image: url(../icons/save.png) !important;
}
@ -286,6 +294,11 @@
.arrow_switch {
background-image: url(../icons/arrow_switch.png) !important;
}
.stream_config {
background-image: url(../icons/film_edit.png) !important;
}
.x-smallhdr {

View file

@ -145,7 +145,6 @@ tvheadend.IdNodeField = function (conf)
width : w,
dataIndex: this.id,
header : this.text,
sortable : true,
editor : this.editor({create: false}),
renderer : this.renderer(),
hidden : this.hidden,
@ -458,6 +457,7 @@ tvheadend.idnode_editor_form = function ( d, panel )
tvheadend.idnode_editor = function(item, conf)
{
var panel = null;
var buttons = [];
/* Buttons */
var saveBtn = new Ext.Button({
@ -476,6 +476,15 @@ tvheadend.idnode_editor = function(item, conf)
});
}
});
buttons.push(saveBtn);
if (conf.help) {
var helpBtn = new Ext.Button({
text : 'Help',
handler : conf.help
});
buttons.push(helpBtn);
}
panel = new Ext.FormPanel({
title : conf.title || null,
@ -490,7 +499,7 @@ tvheadend.idnode_editor = function(item, conf)
//defaults: {width: 330},
defaultType : 'textfield',
buttonAlign : 'left',
buttons : [ saveBtn ]
buttons : buttons
});
tvheadend.idnode_editor_form(item.props || item.params, panel);
@ -655,10 +664,12 @@ tvheadend.idnode_grid = function(panel, conf)
var undoBtn = null;
var addBtn = null;
var delBtn = null;
var upBtn = null;
var downBtn = null;
var editBtn = null;
/* Model */
var idnode = new tvheadend.IdNode(d);
var idnode = new tvheadend.IdNode(d);
for (var i = 0; i < idnode.length(); i++) {
var f = idnode.field(i);
var c = f.column();
@ -698,8 +709,11 @@ tvheadend.idnode_grid = function(panel, conf)
});
/* Model */
var sortable = true;
if (conf.move)
sortable = false;
var model = new Ext.grid.ColumnModel({
defaultSortable : true,
defaultSortable : sortable,
columns : columns
});
@ -717,6 +731,10 @@ tvheadend.idnode_grid = function(panel, conf)
select.on('selectionchange', function(s){
if (delBtn)
delBtn.setDisabled(s.getCount() == 0);
if (upBtn) {
upBtn.setDisabled(s.getCount() == 0);
downBtn.setDisabled(s.getCount() == 0);
}
editBtn.setDisabled(s.getCount() != 1);
if (conf.selected)
conf.selected(s);
@ -799,7 +817,59 @@ tvheadend.idnode_grid = function(panel, conf)
});
buttons.push(delBtn);
}
if (conf.add || conf.del)
if (conf.move) {
upBtn = new Ext.Toolbar.Button({
tooltip : 'Move selected entries up',
iconCls : 'moveup',
text : 'Move Up',
disabled : true,
handler : function() {
var r = select.getSelections();
if (r && r.length > 0) {
var uuids = []
for ( var i = 0; i < r.length; i++ )
uuids.push(r[i].id)
Ext.Ajax.request({
url : 'api/idnode/moveup',
params : {
uuid: Ext.encode(uuids)
},
success : function(d)
{
store.reload();
}
});
}
}
});
buttons.push(upBtn);
downBtn = new Ext.Toolbar.Button({
tooltip : 'Move selected entries down',
iconCls : 'movedown',
text : 'Move Down',
disabled : true,
handler : function() {
var r = select.getSelections();
if (r && r.length > 0) {
var uuids = []
for ( var i = 0; i < r.length; i++ )
uuids.push(r[i].id)
Ext.Ajax.request({
url : 'api/idnode/movedown',
params : {
uuid: Ext.encode(uuids)
},
success : function(d)
{
store.reload();
}
});
}
}
});
buttons.push(downBtn);
}
if (conf.add || conf.del || conf.move)
buttons.push('-');
editBtn = new Ext.Toolbar.Button({
tooltip : 'Edit selected entry',
@ -1037,7 +1107,8 @@ tvheadend.idnode_tree = function (conf)
if(!n.isRoot)
current = panel.add(new tvheadend.idnode_editor(n.attributes, {
title : 'Parameters',
fixedHeight : true
fixedHeight : true,
help : conf.help || null,
}));
panel.doLayout();
}

View file

@ -99,15 +99,6 @@ tvheadend.show_service_streams = function ( data ) {
var i, j;
var html = '';
html += '<table style="font-size:8pt;font-family:mono;padding:2px"';
html += '<tr>';
html += '<th style="width:50px;font-weight:bold">Index</th>';
html += '<th style="width:120px;font-weight:bold">PID</th>';
html += '<th style="width:100px;font-weight:bold">Type</th>';
html += '<th style="width:75px;font-weight:bold">Language</th>';
html += '<th style="width:*;font-weight:bold">Details</th>';
html += '</tr>';
function hexstr ( d ) {
return ('0000' + d.toString(16)).slice(-4);
}
@ -126,8 +117,23 @@ tvheadend.show_service_streams = function ( data ) {
return r;
}
for (i = 0; i < data.streams.length; i++) {
var s = data.streams[i];
function header ( ) {
html += '<table style="font-size:8pt;font-family:mono;padding:2px"';
html += '<tr>';
html += '<th style="width:50px;font-weight:bold">Index</th>';
html += '<th style="width:120px;font-weight:bold">PID</th>';
html += '<th style="width:100px;font-weight:bold">Type</th>';
html += '<th style="width:75px;font-weight:bold">Language</th>';
html += '<th style="width:*;font-weight:bold">Details</th>';
html += '</tr>';
}
function single ( s ) {
html += '<tr><td colspan="5">' + s + '</td></tr>';
}
function stream ( s ) {
var d = '&nbsp;';
var p = '0x' + hexstr(s.pid) + '&nbsp;/&nbsp;' + fixstr(s.pid);
@ -146,16 +152,36 @@ tvheadend.show_service_streams = function ( data ) {
}
html += '<td>' + d + '</td>';
html += '</tr>';
}
}
header();
if (data.streams.length) {
for (i = 0; i < data.streams.length; i++)
stream(data.streams[i]);
} else
single('None');
single('&nbsp;');
single('<h3>After filtering and reordering (without PCR and PMT)</h3>');
header();
if (data.fstreams.length)
for (i = 0; i < data.fstreams.length; i++)
stream(data.fstreams[i]);
else
single('<p>None</p>');
var win = new Ext.Window({
title : 'Service details for ' + data.name,
layout : 'fit',
width : 650,
height : 300,
height : 400,
plain : true,
bodyStyle : 'padding: 5px',
html : html
html : html,
autoScroll: true,
autoShow: true
});
win.show();
}

View file

@ -1,3 +1,10 @@
tvheadend.tvadapters = function() {
return tvheadend.idnode_tree({ url: 'api/hardware/tree', title: 'TV adapters', comet: 'hardware'});
return tvheadend.idnode_tree( {
url : 'api/hardware/tree',
title : 'TV adapters',
comet : 'hardware',
help : function() {
new tvheadend.help('TV adapters', 'config_tvadapters.html');
}
});
}

View file

@ -295,6 +295,17 @@ function accessUpdate(o) {
tabs1.push(tvheadend.conf_csa);
}
/* Stream Config */
tvheadend.conf_stream = new Ext.TabPanel({
activeTab: 0,
autoScroll: true,
title: 'Stream',
iconCls: 'stream_config',
items: []
});
tvheadend.esfilter_tab(tvheadend.conf_stream);
tabs1.push(tvheadend.conf_stream);
/* Debug */
tabs1.push(new tvheadend.tvhlog);

View file

@ -0,0 +1 @@
../../../../vendor/famfamsilk/film_edit.png

82
support/httpc-test.txt Normal file
View file

@ -0,0 +1,82 @@
#
# File format:
# Header= pass this in the http request
# URL= URL
# HandleLocation= Handle the location (redirection) requests (0 or 1)
# DataTransfer=all grab all data and pass then in the data_complete callback
# DataTransfer=cont continuous passing data to the data_received callback
# DataLimit= limit data to these bytes
# ExpectedError= expected error
# SSLPeerVerify= Enable SSL/TLS peer verification (0 or 1)
# Reset=1 reset the initial state - close the current keep-alive conn
# Command= HTTP/RTSP command or EXIT
#
Header=Connection: close
URL=http://www.google.com
DataTransfer=all
HandleLocation=1
Command=GET
Header=Connection: close
URL=http://www.google.com
DataTransfer=all
DataLimit=10
ExpectedError=EOVERFLOW
HandleLocation=1
Command=GET
#
# Keep-alive connection test
#
Reset=1
DataTransfer=all
URL=http://httpbin.org
Command=GET
URL=http://httpbin.org
Command=GET
URL=http://httpbin.org
Command=GET
HandleLocation=1
URL=http://httpbin.org/relative-redirect/20
ExpectedError=ELOOP
Command=GET
#
# Keep-alive SSL test
#
Reset=1
URL=https://httpbin.org
Command=GET
URL=https://httpbin.org
Command=GET
URL=https://httpbin.org
Command=GET
HandleLocation=1
URL=https://httpbin.org/relative-redirect/20
ExpectedError=ELOOP
Command=GET
#
# Google SSL test
#
Reset=1
Header=Connection: close
URL=https://www.google.com
DataTransfer=all
HandleLocation=1
Command=GET
Header=Connection: close
URL=https://www.google.com
DataTransfer=all
DataLimit=10
HandleLocation=1
ExpectedError=EOVERFLOW
Command=GET