Add esfilter (elementary stream filter)

Some feeds (especially from satellite) includes many language mutations
and other elementary streams. It may be useful to define the filters and
order scheme for the streaming and DVR.

The service information dialog is extended to show all PIDs and filtered
PIDs to easy show the used PIDs.

See the included help file for more description.
This commit is contained in:
Jaroslav Kysela 2014-03-07 08:39:00 +01:00
parent 2b37a0610e
commit 47b97e52a8
23 changed files with 1888 additions and 80 deletions

View file

@ -117,6 +117,7 @@ SRCS = src/version.c \
src/rtsp.c \
src/fsmonitor.c \
src/cron.c \
src/esfilter.c
SRCS-${CONFIG_UPNP} += \
src/upnp.c
@ -131,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 \

View file

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

View file

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

View file

@ -67,12 +67,14 @@ void api_epg_init ( void );
void api_epggrab_init ( void );
void api_status_init ( void );
void api_imagecache_init ( void );
void api_esfilter_init ( void );
/*
* IDnode
*/
typedef struct api_idnode_grid_conf
{
int tindex;
int start;
int limit;
idnode_filter_t filter;

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

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

View file

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

View file

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

1023
src/esfilter.c Normal file

File diff suppressed because it is too large Load diff

110
src/esfilter.h Normal file
View file

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

View file

@ -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
* *************************************************************************/

View file

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

View file

@ -61,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"
@ -767,6 +768,7 @@ main(int argc, char **argv)
imagecache_init();
http_client_init();
esfilter_init();
service_init();
@ -877,6 +879,7 @@ 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");

View file

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

View file

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

View file

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

View file

@ -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)

View file

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

View file

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

View file

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

View file

@ -145,7 +145,6 @@ tvheadend.IdNodeField = function (conf)
width : w,
dataIndex: this.id,
header : this.text,
sortable : true,
editor : this.editor({create: false}),
renderer : this.renderer(),
hidden : this.hidden,
@ -665,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();
@ -708,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
});
@ -727,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);
@ -809,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',

View file

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

View file

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

View file

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