This is merge of PR#341 (esfilter) and PR#369 (SAT>IP)
This commit is contained in:
commit
de20cfda59
63 changed files with 8593 additions and 628 deletions
18
Makefile
18
Makefile
|
@ -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
30
configure
vendored
|
@ -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
|
||||
|
||||
#
|
||||
|
|
109
docs/html/config_esfilter.html
Normal file
109
docs/html/config_esfilter.html
Normal 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>
|
75
docs/html/config_tvadapters.html
Normal file
75
docs/html/config_tvadapters.html
Normal 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>
|
|
@ -125,6 +125,7 @@ void api_init ( void )
|
|||
api_epggrab_init();
|
||||
api_status_init();
|
||||
api_imagecache_init();
|
||||
api_esfilter_init();
|
||||
}
|
||||
|
||||
void api_done ( void )
|
||||
|
|
|
@ -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
101
src/api/api_esfilter.c
Normal 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);
|
||||
}
|
|
@ -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 },
|
||||
};
|
||||
|
||||
|
|
|
@ -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
1023
src/esfilter.c
Normal file
File diff suppressed because it is too large
Load diff
110
src/esfilter.h
Normal file
110
src/esfilter.h
Normal 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__ */
|
32
src/http.c
32
src/http.c
|
@ -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;
|
||||
|
|
264
src/http.h
264
src/http.h
|
@ -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_ */
|
||||
|
|
|
@ -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
1715
src/httpc.c
Normal file
File diff suppressed because it is too large
Load diff
29
src/idnode.c
29
src/idnode.c
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
17
src/idnode.h
17
src/idnode.h
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
921
src/input/mpegts/satip/satip.c
Normal file
921
src/input/mpegts/satip/satip.c
Normal 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);
|
||||
}
|
26
src/input/mpegts/satip/satip.h
Normal file
26
src/input/mpegts/satip/satip.h
Normal 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__ */
|
1398
src/input/mpegts/satip/satip_frontend.c
Normal file
1398
src/input/mpegts/satip/satip_frontend.c
Normal file
File diff suppressed because it is too large
Load diff
218
src/input/mpegts/satip/satip_private.h
Normal file
218
src/input/mpegts/satip/satip_private.h
Normal 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__ */
|
282
src/input/mpegts/satip/satip_rtsp.c
Normal file
282
src/input/mpegts/satip/satip_rtsp.c
Normal 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;
|
||||
}
|
327
src/input/mpegts/satip/satip_satconf.c
Normal file
327
src/input/mpegts/satip/satip_satconf.c
Normal 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
|
||||
*****************************************************************************/
|
|
@ -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;
|
||||
}
|
||||
|
|
53
src/main.c
53
src/main.c
|
@ -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
199
src/rtsp.c
Normal 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;
|
||||
}
|
183
src/service.c
183
src/service.c
|
@ -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;
|
||||
|
|
|
@ -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)\
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
485
src/udp.c
Normal 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
91
src/udp.h
Normal 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
219
src/upnp.c
Normal 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
47
src/upnp.h
Normal 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
107
src/url.c
|
@ -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 */
|
||||
|
|
24
src/url.h
24
src/url.h
|
@ -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
|
||||
|
|
35
src/utils.c
35
src/utils.c
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
108
src/webui/static/app/esfilter.js
Normal file
108
src/webui/static/app/esfilter.js
Normal 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');
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 = ' ';
|
||||
var p = '0x' + hexstr(s.pid) + ' / ' + 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(' ');
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
1
src/webui/static/icons/film_edit.png
Symbolic link
1
src/webui/static/icons/film_edit.png
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../../vendor/famfamsilk/film_edit.png
|
82
support/httpc-test.txt
Normal file
82
support/httpc-test.txt
Normal 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
|
Loading…
Add table
Reference in a new issue