Add stream profile support
This commit is contained in:
parent
29d5c75126
commit
2012dc8e3d
27 changed files with 1101 additions and 316 deletions
8
Makefile
8
Makefile
|
@ -122,7 +122,7 @@ SRCS = src/version.c \
|
|||
src/trap.c \
|
||||
src/avg.c \
|
||||
src/htsstr.c \
|
||||
src/tvhpoll.c \
|
||||
src/tvhpoll.c \
|
||||
src/huffman.c \
|
||||
src/filebundle.c \
|
||||
src/config.c \
|
||||
|
@ -137,7 +137,8 @@ SRCS = src/version.c \
|
|||
src/fsmonitor.c \
|
||||
src/cron.c \
|
||||
src/esfilter.c \
|
||||
src/intlconv.c
|
||||
src/intlconv.c \
|
||||
src/profile.c
|
||||
|
||||
SRCS-${CONFIG_UPNP} += \
|
||||
src/upnp.c
|
||||
|
@ -157,7 +158,8 @@ SRCS += \
|
|||
src/api/api_intlconv.c \
|
||||
src/api/api_access.c \
|
||||
src/api/api_dvr.c \
|
||||
src/api/api_caclient.c
|
||||
src/api/api_caclient.c \
|
||||
src/api/api_profile.c
|
||||
|
||||
SRCS += \
|
||||
src/parsers/parsers.c \
|
||||
|
|
|
@ -134,6 +134,7 @@ void api_init ( void )
|
|||
api_access_init();
|
||||
api_dvr_init();
|
||||
api_caclient_init();
|
||||
api_profile_init();
|
||||
}
|
||||
|
||||
void api_done ( void )
|
||||
|
|
|
@ -74,6 +74,7 @@ void api_intlconv_init ( void );
|
|||
void api_access_init ( void );
|
||||
void api_dvr_init ( void );
|
||||
void api_caclient_init ( void );
|
||||
void api_profile_init ( void );
|
||||
|
||||
/*
|
||||
* IDnode
|
||||
|
|
|
@ -34,6 +34,7 @@ api_caclient_list
|
|||
htsmsg_t *l, *e;
|
||||
|
||||
l = htsmsg_create_list();
|
||||
pthread_mutex_lock(&global_lock);
|
||||
TAILQ_FOREACH(cac, &caclients, cac_link) {
|
||||
e = htsmsg_create_map();
|
||||
htsmsg_add_str(e, "uuid", idnode_uuid_as_str(&cac->cac_id));
|
||||
|
@ -41,6 +42,7 @@ api_caclient_list
|
|||
htsmsg_add_str(e, "status", caclient_get_status(cac));
|
||||
htsmsg_add_msg(l, NULL, e);
|
||||
}
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
*resp = htsmsg_create_map();
|
||||
htsmsg_add_msg(*resp, "entries", l);
|
||||
return 0;
|
||||
|
|
110
src/api/api_profile.c
Normal file
110
src/api/api_profile.c
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* tvheadend - API access to Stream Profile
|
||||
*
|
||||
* 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 "access.h"
|
||||
#include "htsmsg.h"
|
||||
#include "api.h"
|
||||
#include "profile.h"
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static int
|
||||
api_profile_list
|
||||
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||
{
|
||||
profile_t *pro;
|
||||
htsmsg_t *l, *e;
|
||||
|
||||
l = htsmsg_create_list();
|
||||
pthread_mutex_lock(&global_lock);
|
||||
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
||||
e = htsmsg_create_map();
|
||||
htsmsg_add_str(e, "key", idnode_uuid_as_str(&pro->pro_id));
|
||||
htsmsg_add_str(e, "val", profile_get_name(pro));
|
||||
htsmsg_add_msg(l, NULL, e);
|
||||
}
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
*resp = htsmsg_create_map();
|
||||
htsmsg_add_msg(*resp, "entries", l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
api_profile_builders
|
||||
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||
{
|
||||
profile_build_t *pb;
|
||||
htsmsg_t *l, *e;
|
||||
|
||||
l = htsmsg_create_list();
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
/* List of available builder classes */
|
||||
LIST_FOREACH(pb, &profile_builders, link)
|
||||
if ((e = idclass_serialize(pb->clazz)))
|
||||
htsmsg_add_msg(l, NULL, e);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
/* Output */
|
||||
*resp = htsmsg_create_map();
|
||||
htsmsg_add_msg(*resp, "entries", l);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
api_profile_create
|
||||
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||
{
|
||||
int err = 0;
|
||||
const char *clazz;
|
||||
htsmsg_t *conf;
|
||||
|
||||
if (!(clazz = htsmsg_get_str(args, "class")))
|
||||
return EINVAL;
|
||||
if (!(conf = htsmsg_get_map(args, "conf")))
|
||||
return EINVAL;
|
||||
htsmsg_set_str(conf, "class", clazz);
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
if (profile_create(NULL, conf, 1) == NULL)
|
||||
err = -EINVAL;
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init
|
||||
*/
|
||||
void
|
||||
api_profile_init ( void )
|
||||
{
|
||||
static api_hook_t ah[] = {
|
||||
{ "profile/list", ACCESS_ADMIN, api_profile_list, NULL },
|
||||
{ "profile/class", ACCESS_ADMIN, api_idnode_class, (void*)&profile_class },
|
||||
{ "profile/builders", ACCESS_ADMIN, api_profile_builders, NULL },
|
||||
{ "profile/create", ACCESS_ADMIN, api_profile_create, NULL },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
api_register_all(ah);
|
||||
}
|
|
@ -24,16 +24,19 @@
|
|||
#include "channels.h"
|
||||
#include "subscriptions.h"
|
||||
#include "muxer.h"
|
||||
#include "profile.h"
|
||||
#include "lang_str.h"
|
||||
|
||||
typedef struct dvr_config {
|
||||
idnode_t dvr_id;
|
||||
|
||||
LIST_ENTRY(dvr_config) config_link;
|
||||
LIST_ENTRY(dvr_config) profile_link;
|
||||
|
||||
int dvr_enabled;
|
||||
int dvr_valid;
|
||||
char *dvr_config_name;
|
||||
profile_t *dvr_profile;
|
||||
char *dvr_storage;
|
||||
uint32_t dvr_retention_days;
|
||||
char *dvr_charset;
|
||||
|
@ -43,7 +46,6 @@ typedef struct dvr_config {
|
|||
uint32_t dvr_extra_time_post;
|
||||
uint32_t dvr_update_window;
|
||||
|
||||
int dvr_mc;
|
||||
muxer_config_t dvr_muxcnf;
|
||||
|
||||
int dvr_dir_per_day;
|
||||
|
@ -208,15 +210,11 @@ typedef struct dvr_entry {
|
|||
pthread_t de_thread;
|
||||
|
||||
th_subscription_t *de_s;
|
||||
streaming_queue_t de_sq;
|
||||
streaming_target_t *de_tsfix;
|
||||
streaming_target_t *de_gh;
|
||||
|
||||
/**
|
||||
* Initialized upon SUBSCRIPTION_TRANSPORT_RUN
|
||||
*/
|
||||
|
||||
struct muxer *de_mux;
|
||||
/**
|
||||
* Stream worker chain
|
||||
*/
|
||||
profile_chain_t *de_chain;
|
||||
|
||||
/**
|
||||
* Inotify
|
||||
|
@ -350,6 +348,8 @@ void dvr_config_delete(const char *name);
|
|||
|
||||
void dvr_config_save(dvr_config_t *cfg);
|
||||
|
||||
void dvr_config_destroy_by_profile(profile_t *pro, int delconf);
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -139,7 +139,6 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf)
|
|||
cfg->dvr_enabled = 1;
|
||||
cfg->dvr_config_name = strdup(name);
|
||||
cfg->dvr_retention_days = 31;
|
||||
cfg->dvr_mc = MC_MATROSKA;
|
||||
cfg->dvr_tag_files = 1;
|
||||
cfg->dvr_skip_commercials = 1;
|
||||
dvr_charset_update(cfg, intlconv_filesystem_charset());
|
||||
|
@ -155,7 +154,6 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf)
|
|||
|
||||
/* Muxer config */
|
||||
cfg->dvr_muxcnf.m_cache = MC_CACHE_DONTKEEP;
|
||||
cfg->dvr_muxcnf.m_rewrite_pat = 1;
|
||||
|
||||
/* dup detect */
|
||||
cfg->dvr_dup_detect_episode = 1; // detect dup episodes
|
||||
|
@ -172,6 +170,12 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf)
|
|||
|
||||
tvhinfo("dvr", "Creating new configuration '%s'", cfg->dvr_config_name);
|
||||
|
||||
if (cfg->dvr_profile == NULL) {
|
||||
cfg->dvr_profile = profile_find_by_name(NULL);
|
||||
assert(cfg->dvr_profile);
|
||||
LIST_INSERT_HEAD(&cfg->dvr_profile->pro_dvr_configs, cfg, profile_link);
|
||||
}
|
||||
|
||||
if (dvr_config_is_default(cfg) && dvr_config_find_by_name(NULL)) {
|
||||
tvherror("dvr", "Unable to create second default config, removing");
|
||||
LIST_INSERT_HEAD(&dvrconfigs, cfg, config_link);
|
||||
|
@ -201,6 +205,11 @@ dvr_config_destroy(dvr_config_t *cfg, int delconf)
|
|||
LIST_REMOVE(cfg, config_link);
|
||||
idnode_unlink(&cfg->dvr_id);
|
||||
|
||||
if (cfg->dvr_profile) {
|
||||
LIST_REMOVE(cfg, profile_link);
|
||||
cfg->dvr_profile = NULL;
|
||||
}
|
||||
|
||||
dvr_entry_destroy_by_config(cfg, delconf);
|
||||
access_destroy_by_dvr_config(cfg, delconf);
|
||||
autorec_destroy_by_config(cfg, delconf);
|
||||
|
@ -327,6 +336,51 @@ dvr_config_class_name_set(void *o, const void *v)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dvr_config_class_profile_set(void *o, const void *v)
|
||||
{
|
||||
dvr_config_t *cfg = (dvr_config_t *)o;
|
||||
profile_t *pro;
|
||||
|
||||
pro = v ? profile_find_by_uuid(v) : NULL;
|
||||
pro = pro ?: profile_find_by_name(v);
|
||||
if (pro == NULL) {
|
||||
if (cfg->dvr_profile) {
|
||||
LIST_REMOVE(cfg, profile_link);
|
||||
cfg->dvr_profile = NULL;
|
||||
return 1;
|
||||
}
|
||||
} else if (cfg->dvr_profile != pro) {
|
||||
if (cfg->dvr_profile)
|
||||
LIST_REMOVE(cfg, profile_link);
|
||||
cfg->dvr_profile = pro;
|
||||
LIST_INSERT_HEAD(&pro->pro_dvr_configs, cfg, profile_link);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const void *
|
||||
dvr_config_class_profile_get(void *o)
|
||||
{
|
||||
static const char *ret;
|
||||
dvr_config_t *cfg = (dvr_config_t *)o;
|
||||
if (cfg->dvr_profile)
|
||||
ret = idnode_uuid_as_str(&cfg->dvr_profile->pro_id);
|
||||
else
|
||||
ret = "";
|
||||
return &ret;
|
||||
}
|
||||
|
||||
static char *
|
||||
dvr_config_class_profile_rend(void *o)
|
||||
{
|
||||
dvr_config_t *cfg = (dvr_config_t *)o;
|
||||
if (cfg->dvr_profile)
|
||||
return strdup(profile_get_name(cfg->dvr_profile));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *
|
||||
dvr_config_class_get_title (idnode_t *self)
|
||||
{
|
||||
|
@ -433,12 +487,14 @@ const idclass_t dvr_config_class = {
|
|||
.get_opts = dvr_config_class_enabled_opts,
|
||||
},
|
||||
{
|
||||
.type = PT_INT,
|
||||
.id = "container",
|
||||
.name = "Container",
|
||||
.off = offsetof(dvr_config_t, dvr_mc),
|
||||
.def.i = MC_MATROSKA,
|
||||
.list = dvr_entry_class_mc_list,
|
||||
.type = PT_STR,
|
||||
.id = "profile",
|
||||
.name = "Stream Profile",
|
||||
.off = offsetof(dvr_config_t, dvr_profile),
|
||||
.set = dvr_config_class_profile_set,
|
||||
.get = dvr_config_class_profile_get,
|
||||
.rend = dvr_config_class_profile_rend,
|
||||
.list = profile_class_get_list,
|
||||
.group = 1,
|
||||
},
|
||||
{
|
||||
|
@ -522,21 +578,6 @@ const idclass_t dvr_config_class = {
|
|||
.def.s = "UTF-8",
|
||||
.group = 2,
|
||||
},
|
||||
{
|
||||
.type = PT_BOOL,
|
||||
.id = "rewrite-pat",
|
||||
.name = "Rewrite PAT",
|
||||
.off = offsetof(dvr_config_t, dvr_muxcnf.m_rewrite_pat),
|
||||
.def.i = 1,
|
||||
.group = 2,
|
||||
},
|
||||
{
|
||||
.type = PT_BOOL,
|
||||
.id = "rewrite-pmt",
|
||||
.name = "Rewrite PMT",
|
||||
.off = offsetof(dvr_config_t, dvr_muxcnf.m_rewrite_pmt),
|
||||
.group = 2,
|
||||
},
|
||||
{
|
||||
.type = PT_BOOL,
|
||||
.id = "tag-files",
|
||||
|
@ -649,6 +690,20 @@ const idclass_t dvr_config_class = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvr_config_destroy_by_profile(profile_t *pro, int delconf)
|
||||
{
|
||||
dvr_config_t *cfg;
|
||||
|
||||
while((cfg = LIST_FIRST(&pro->pro_dvr_configs)) != NULL) {
|
||||
LIST_REMOVE(cfg, profile_link);
|
||||
cfg->dvr_profile = profile_find_by_name(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -103,7 +103,7 @@ dvr_entry_get_mc( dvr_entry_t *de )
|
|||
{
|
||||
if (de->de_mc >= 0)
|
||||
return de->de_mc;
|
||||
return de->de_config->dvr_mc;
|
||||
return profile_get_mc(de->de_config->dvr_profile);
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -1779,7 +1779,7 @@ const idclass_t dvr_entry_class = {
|
|||
.def.i = MC_MATROSKA,
|
||||
.set = dvr_entry_class_mc_set,
|
||||
.list = dvr_entry_class_mc_list,
|
||||
.opts = PO_SORTKEY
|
||||
.opts = PO_RDONLY
|
||||
},
|
||||
{
|
||||
.type = PT_STR,
|
||||
|
|
|
@ -63,10 +63,11 @@ dvr_rec_subscribe(dvr_entry_t *de)
|
|||
{
|
||||
char buf[100];
|
||||
int weight;
|
||||
streaming_target_t *st;
|
||||
int flags;
|
||||
profile_t *pro;
|
||||
profile_chain_t *prch;
|
||||
|
||||
assert(de->de_s == NULL);
|
||||
assert(de->de_chain == NULL);
|
||||
|
||||
if(de->de_pri < ARRAY_SIZE(prio2weight))
|
||||
weight = prio2weight[de->de_pri];
|
||||
|
@ -75,23 +76,28 @@ dvr_rec_subscribe(dvr_entry_t *de)
|
|||
|
||||
snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL));
|
||||
|
||||
if(dvr_entry_get_mc(de) == MC_PASS) {
|
||||
streaming_queue_init(&de->de_sq, SMT_PACKET);
|
||||
de->de_gh = NULL;
|
||||
de->de_tsfix = NULL;
|
||||
st = &de->de_sq.sq_st;
|
||||
flags = SUBSCRIPTION_RAW_MPEGTS;
|
||||
} else {
|
||||
streaming_queue_init(&de->de_sq, 0);
|
||||
de->de_gh = globalheaders_create(&de->de_sq.sq_st);
|
||||
st = de->de_tsfix = tsfix_create(de->de_gh);
|
||||
tsfix_set_start_time(de->de_tsfix, dvr_entry_get_start_time(de));
|
||||
flags = 0;
|
||||
pro = de->de_config->dvr_profile;
|
||||
prch = malloc(sizeof(*prch));
|
||||
if (pro->pro_open(pro, prch, &de->de_config->dvr_muxcnf, 0)) {
|
||||
tvherror("dvr", "unable to create new channel streaming chain for '%s'",
|
||||
channel_get_name(de->de_channel));
|
||||
return;
|
||||
}
|
||||
|
||||
de->de_s = subscription_create_from_channel(de->de_channel, weight,
|
||||
buf, st, flags,
|
||||
buf, prch->prch_st,
|
||||
prch->prch_flags,
|
||||
NULL, NULL, NULL);
|
||||
if (de->de_s == NULL) {
|
||||
tvherror("dvr", "unable to create new channel subcription for '%s'",
|
||||
channel_get_name(de->de_channel));
|
||||
profile_chain_close(prch);
|
||||
free(prch);
|
||||
de->de_chain = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
de->de_chain = prch;
|
||||
|
||||
tvhthread_create(&de->de_thread, NULL, dvr_thread, de);
|
||||
}
|
||||
|
@ -102,20 +108,21 @@ dvr_rec_subscribe(dvr_entry_t *de)
|
|||
void
|
||||
dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
|
||||
{
|
||||
assert(de->de_s != NULL);
|
||||
profile_chain_t *prch = de->de_chain;
|
||||
|
||||
streaming_target_deliver(&de->de_sq.sq_st, streaming_msg_create(SMT_EXIT));
|
||||
assert(de->de_s != NULL);
|
||||
assert(prch != NULL);
|
||||
|
||||
streaming_target_deliver(prch->prch_st, streaming_msg_create(SMT_EXIT));
|
||||
|
||||
pthread_join(de->de_thread, NULL);
|
||||
|
||||
subscription_unsubscribe(de->de_s);
|
||||
de->de_s = NULL;
|
||||
|
||||
if(de->de_tsfix)
|
||||
tsfix_destroy(de->de_tsfix);
|
||||
|
||||
if(de->de_gh)
|
||||
globalheaders_destroy(de->de_gh);
|
||||
de->de_chain = NULL;
|
||||
profile_chain_close(prch);
|
||||
free(prch);
|
||||
|
||||
de->de_last_error = stopcode;
|
||||
}
|
||||
|
@ -235,7 +242,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
if (filename == NULL)
|
||||
return -1;
|
||||
snprintf(fullname, sizeof(fullname), "%s/%s.%s",
|
||||
path, filename, muxer_suffix(de->de_mux, ss));
|
||||
path, filename, muxer_suffix(de->de_chain->prch_muxer, ss));
|
||||
|
||||
while(1) {
|
||||
if(stat(fullname, &st) == -1) {
|
||||
|
@ -250,7 +257,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
tally++;
|
||||
|
||||
snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s",
|
||||
path, filename, tally, muxer_suffix(de->de_mux, ss));
|
||||
path, filename, tally, muxer_suffix(de->de_chain->prch_muxer, ss));
|
||||
}
|
||||
free(filename);
|
||||
|
||||
|
@ -310,17 +317,23 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
const streaming_start_component_t *ssc;
|
||||
int i;
|
||||
dvr_config_t *cfg = de->de_config;
|
||||
muxer_container_type_t mc;
|
||||
profile_chain_t *prch = de->de_chain;
|
||||
muxer_t *muxer;
|
||||
|
||||
if (!cfg) {
|
||||
dvr_rec_fatal_error(de, "Unable to determine config profile");
|
||||
return -1;
|
||||
}
|
||||
|
||||
mc = dvr_entry_get_mc(de);
|
||||
if (!prch) {
|
||||
dvr_rec_fatal_error(de, "Unable to determine stream profile");
|
||||
return -1;
|
||||
}
|
||||
|
||||
de->de_mux = muxer_create(mc, &cfg->dvr_muxcnf);
|
||||
if(!de->de_mux) {
|
||||
if (!(muxer = prch->prch_muxer))
|
||||
muxer = prch->prch_muxer = muxer_create(&cfg->dvr_muxcnf);
|
||||
|
||||
if(!muxer) {
|
||||
dvr_rec_fatal_error(de, "Unable to create muxer");
|
||||
return -1;
|
||||
}
|
||||
|
@ -330,18 +343,18 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
return -1;
|
||||
}
|
||||
|
||||
if(muxer_open_file(de->de_mux, de->de_filename)) {
|
||||
if(muxer_open_file(muxer, de->de_filename)) {
|
||||
dvr_rec_fatal_error(de, "Unable to open file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(muxer_init(de->de_mux, ss, lang_str_get(de->de_title, NULL))) {
|
||||
if(muxer_init(muxer, ss, lang_str_get(de->de_title, NULL))) {
|
||||
dvr_rec_fatal_error(de, "Unable to init file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(cfg->dvr_tag_files && de->de_bcast) {
|
||||
if(muxer_write_meta(de->de_mux, de->de_bcast)) {
|
||||
if(muxer_write_meta(muxer, de->de_bcast)) {
|
||||
dvr_rec_fatal_error(de, "Unable to write meta data");
|
||||
return -1;
|
||||
}
|
||||
|
@ -438,7 +451,8 @@ dvr_thread(void *aux)
|
|||
{
|
||||
dvr_entry_t *de = aux;
|
||||
dvr_config_t *cfg = de->de_config;
|
||||
streaming_queue_t *sq = &de->de_sq;
|
||||
profile_chain_t *prch = de->de_chain;
|
||||
streaming_queue_t *sq = &prch->prch_sq;
|
||||
streaming_message_t *sm;
|
||||
th_pkt_t *pkt;
|
||||
int run = 1;
|
||||
|
@ -482,12 +496,12 @@ dvr_thread(void *aux)
|
|||
break;
|
||||
|
||||
if(commercial != pkt->pkt_commercial)
|
||||
muxer_add_marker(de->de_mux);
|
||||
muxer_add_marker(prch->prch_muxer);
|
||||
|
||||
commercial = pkt->pkt_commercial;
|
||||
|
||||
if(started) {
|
||||
muxer_write_pkt(de->de_mux, sm->sm_type, sm->sm_data);
|
||||
muxer_write_pkt(prch->prch_muxer, sm->sm_type, sm->sm_data);
|
||||
sm->sm_data = NULL;
|
||||
}
|
||||
break;
|
||||
|
@ -495,14 +509,14 @@ dvr_thread(void *aux)
|
|||
case SMT_MPEGTS:
|
||||
if(started) {
|
||||
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
|
||||
muxer_write_pkt(de->de_mux, sm->sm_type, sm->sm_data);
|
||||
muxer_write_pkt(prch->prch_muxer, sm->sm_type, sm->sm_data);
|
||||
sm->sm_data = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case SMT_START:
|
||||
if(started &&
|
||||
muxer_reconfigure(de->de_mux, sm->sm_data) < 0) {
|
||||
muxer_reconfigure(prch->prch_muxer, sm->sm_data) < 0) {
|
||||
tvhlog(LOG_WARNING,
|
||||
"dvr", "Unable to reconfigure \"%s\"",
|
||||
de->de_filename ?: lang_str_get(de->de_title, NULL));
|
||||
|
@ -607,7 +621,7 @@ dvr_thread(void *aux)
|
|||
}
|
||||
pthread_mutex_unlock(&sq->sq_mutex);
|
||||
|
||||
if(de->de_mux)
|
||||
if(prch->prch_muxer)
|
||||
dvr_thread_epilog(de);
|
||||
|
||||
return NULL;
|
||||
|
@ -671,9 +685,11 @@ dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc)
|
|||
static void
|
||||
dvr_thread_epilog(dvr_entry_t *de)
|
||||
{
|
||||
muxer_close(de->de_mux);
|
||||
muxer_destroy(de->de_mux);
|
||||
de->de_mux = NULL;
|
||||
profile_chain_t *prch = de->de_chain;
|
||||
|
||||
muxer_close(prch->prch_muxer);
|
||||
muxer_destroy(prch->prch_muxer);
|
||||
prch->prch_muxer = NULL;
|
||||
|
||||
dvr_config_t *cfg = de->de_config;
|
||||
if(cfg && cfg->dvr_postproc && de->de_filename)
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
#include "libav.h"
|
||||
#include "plumbing/transcoding.h"
|
||||
#endif
|
||||
#include "profile.h"
|
||||
|
||||
#ifdef PLATFORM_LINUX
|
||||
#include <sys/prctl.h>
|
||||
|
@ -816,6 +817,8 @@ main(int argc, char **argv)
|
|||
transcoding_init();
|
||||
#endif
|
||||
|
||||
profile_init();
|
||||
|
||||
imagecache_init();
|
||||
|
||||
http_client_init(opt_user_agent);
|
||||
|
@ -939,6 +942,7 @@ main(int argc, char **argv)
|
|||
tvhftrace("main", dvb_done);
|
||||
tvhftrace("main", lang_str_done);
|
||||
tvhftrace("main", esfilter_done);
|
||||
tvhftrace("main", profile_done);
|
||||
tvhftrace("main", intlconv_done);
|
||||
tvhftrace("main", urlparse_done);
|
||||
tvhftrace("main", idnode_done);
|
||||
|
|
12
src/muxer.c
12
src/muxer.c
|
@ -159,6 +159,7 @@ muxer_container_type2txt(muxer_container_type_t mc)
|
|||
}
|
||||
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* Get a list of supported containers
|
||||
*/
|
||||
|
@ -190,6 +191,7 @@ muxer_container_list(htsmsg_t *array)
|
|||
|
||||
return c;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
|
@ -237,25 +239,25 @@ muxer_container_mime2type(const char *str)
|
|||
* Create a new muxer
|
||||
*/
|
||||
muxer_t*
|
||||
muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
||||
muxer_create(const muxer_config_t *m_cfg)
|
||||
{
|
||||
muxer_t *m;
|
||||
|
||||
assert(m_cfg);
|
||||
|
||||
m = pass_muxer_create(mc, m_cfg);
|
||||
m = pass_muxer_create(m_cfg);
|
||||
|
||||
if(!m)
|
||||
m = tvh_muxer_create(mc, m_cfg);
|
||||
m = tvh_muxer_create(m_cfg);
|
||||
|
||||
#if CONFIG_LIBAV
|
||||
if(!m)
|
||||
m = lav_muxer_create(mc, m_cfg);
|
||||
m = lav_muxer_create(m_cfg);
|
||||
#endif
|
||||
|
||||
if(!m) {
|
||||
tvhlog(LOG_ERR, "mux", "Can't find a muxer that supports '%s' container",
|
||||
muxer_container_type2txt(mc));
|
||||
muxer_container_type2txt(m_cfg->m_type));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ typedef enum {
|
|||
|
||||
/* Muxer configuration used when creating a muxer. */
|
||||
typedef struct muxer_config {
|
||||
int m_type; /* MC_* */
|
||||
|
||||
int m_rewrite_pat;
|
||||
int m_rewrite_pmt;
|
||||
int m_cache;
|
||||
|
@ -83,7 +85,6 @@ typedef struct muxer {
|
|||
|
||||
int m_eos; // End of stream
|
||||
int m_errors; // Number of errors
|
||||
muxer_container_type_t m_container; // The type of the container
|
||||
muxer_config_t m_config; // general configuration
|
||||
} muxer_t;
|
||||
|
||||
|
@ -97,10 +98,10 @@ muxer_container_type_t muxer_container_mime2type (const char *str);
|
|||
|
||||
const char* muxer_container_suffix(muxer_container_type_t mc, int video);
|
||||
|
||||
int muxer_container_list(htsmsg_t *array);
|
||||
//int muxer_container_list(htsmsg_t *array);
|
||||
|
||||
// Muxer factory
|
||||
muxer_t *muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg);
|
||||
muxer_t *muxer_create(const muxer_config_t *m_cfg);
|
||||
|
||||
// Wrapper functions
|
||||
int muxer_open_file (muxer_t *m, const char *filename);
|
||||
|
|
|
@ -75,7 +75,7 @@ lav_muxer_add_stream(lav_muxer_t *lm,
|
|||
c = st->codec;
|
||||
c->codec_id = streaming_component_type2codec_id(ssc->ssc_type);
|
||||
|
||||
switch(lm->m_container) {
|
||||
switch(lm->m_config.m_type) {
|
||||
case MC_MATROSKA:
|
||||
st->time_base.num = 1000000;
|
||||
st->time_base.den = 1;
|
||||
|
@ -205,7 +205,7 @@ lav_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
|||
if(ssc->ssc_disabled)
|
||||
continue;
|
||||
|
||||
if(!lav_muxer_support_stream(m->m_container, ssc->ssc_type))
|
||||
if(!lav_muxer_support_stream(m->m_config.m_type, ssc->ssc_type))
|
||||
continue;
|
||||
|
||||
has_video |= SCT_ISVIDEO(ssc->ssc_type);
|
||||
|
@ -213,9 +213,9 @@ lav_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
|||
}
|
||||
|
||||
if(has_video)
|
||||
return muxer_container_type2mime(m->m_container, 1);
|
||||
return muxer_container_type2mime(m->m_config.m_type, 1);
|
||||
else if(has_audio)
|
||||
return muxer_container_type2mime(m->m_container, 0);
|
||||
return muxer_container_type2mime(m->m_config.m_type, 0);
|
||||
else
|
||||
return muxer_container_type2mime(MC_UNKNOWN, 0);
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
|
|||
av_dict_set(&oc->metadata, "service_name", name, 0);
|
||||
av_dict_set(&oc->metadata, "service_provider", app, 0);
|
||||
|
||||
if(lm->m_container == MC_MPEGTS)
|
||||
if(lm->m_config.m_type == MC_MPEGTS)
|
||||
lm->lm_h264_filter = av_bitstream_filter_init("h264_mp4toannexb");
|
||||
|
||||
oc->max_delay = 0.7 * AV_TIME_BASE;
|
||||
|
@ -252,10 +252,10 @@ lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
|
|||
if(ssc->ssc_disabled)
|
||||
continue;
|
||||
|
||||
if(!lav_muxer_support_stream(lm->m_container, ssc->ssc_type)) {
|
||||
if(!lav_muxer_support_stream(lm->m_config.m_type, ssc->ssc_type)) {
|
||||
tvhlog(LOG_WARNING, "libav", "%s is not supported in %s",
|
||||
streaming_component_type2txt(ssc->ssc_type),
|
||||
muxer_container_type2txt(lm->m_container));
|
||||
muxer_container_type2txt(lm->m_config.m_type));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
|
|||
return -1;
|
||||
} else if(avformat_write_header(lm->lm_oc, NULL) < 0) {
|
||||
tvhlog(LOG_ERR, "libav", "Failed to write %s header",
|
||||
muxer_container_type2txt(lm->m_container));
|
||||
muxer_container_type2txt(lm->m_config.m_type));
|
||||
lm->m_errors++;
|
||||
return -1;
|
||||
}
|
||||
|
@ -454,7 +454,7 @@ lav_muxer_close(muxer_t *m)
|
|||
|
||||
if(lm->lm_init && av_write_trailer(lm->lm_oc) < 0) {
|
||||
tvhlog(LOG_WARNING, "libav", "Failed to write %s trailer",
|
||||
muxer_container_type2txt(lm->m_container));
|
||||
muxer_container_type2txt(lm->m_config.m_type));
|
||||
lm->m_errors++;
|
||||
ret = -1;
|
||||
}
|
||||
|
@ -493,18 +493,18 @@ lav_muxer_destroy(muxer_t *m)
|
|||
* Create a new libavformat based muxer
|
||||
*/
|
||||
muxer_t*
|
||||
lav_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
||||
lav_muxer_create(const muxer_config_t *m_cfg)
|
||||
{
|
||||
const char *mux_name;
|
||||
lav_muxer_t *lm;
|
||||
AVOutputFormat *fmt;
|
||||
|
||||
switch(mc) {
|
||||
switch(m_cfg->m_type) {
|
||||
case MC_MPEGPS:
|
||||
mux_name = "dvd";
|
||||
break;
|
||||
default:
|
||||
mux_name = muxer_container_type2txt(mc);
|
||||
mux_name = muxer_container_type2txt(m_cfg->m_type);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -525,7 +525,6 @@ lav_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
|||
lm->m_write_pkt = lav_muxer_write_pkt;
|
||||
lm->m_close = lav_muxer_close;
|
||||
lm->m_destroy = lav_muxer_destroy;
|
||||
lm->m_container = mc;
|
||||
lm->lm_oc = avformat_alloc_context();
|
||||
lm->lm_oc->oformat = fmt;
|
||||
lm->lm_fd = -1;
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
#include "muxer.h"
|
||||
|
||||
muxer_t* lav_muxer_create
|
||||
(muxer_container_type_t mc, const muxer_config_t* m_cfg);
|
||||
muxer_t* lav_muxer_create (const muxer_config_t* m_cfg);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -562,11 +562,11 @@ pass_muxer_destroy(muxer_t *m)
|
|||
* Create a new passthrough muxer
|
||||
*/
|
||||
muxer_t*
|
||||
pass_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
||||
pass_muxer_create(const muxer_config_t *m_cfg)
|
||||
{
|
||||
pass_muxer_t *pm;
|
||||
|
||||
if(mc != MC_PASS && mc != MC_RAW)
|
||||
if(m_cfg->m_type != MC_PASS && m_cfg->m_type != MC_RAW)
|
||||
return NULL;
|
||||
|
||||
pm = calloc(1, sizeof(pass_muxer_t));
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
#include "muxer.h"
|
||||
|
||||
muxer_t* pass_muxer_create
|
||||
(muxer_container_type_t mc, const muxer_config_t* m_cfg);
|
||||
muxer_t* pass_muxer_create (const muxer_config_t* m_cfg);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -56,9 +56,9 @@ tvh_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
|||
}
|
||||
|
||||
if(has_video)
|
||||
return muxer_container_type2mime(m->m_container, 1);
|
||||
return muxer_container_type2mime(m->m_config.m_type, 1);
|
||||
else if(has_audio)
|
||||
return muxer_container_type2mime(m->m_container, 0);
|
||||
return muxer_container_type2mime(m->m_config.m_type, 0);
|
||||
else
|
||||
return muxer_container_type2mime(MC_UNKNOWN, 0);
|
||||
}
|
||||
|
@ -223,11 +223,11 @@ tvh_muxer_destroy(muxer_t *m)
|
|||
* Create a new builtin muxer
|
||||
*/
|
||||
muxer_t*
|
||||
tvh_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
||||
tvh_muxer_create(const muxer_config_t *m_cfg)
|
||||
{
|
||||
tvh_muxer_t *tm;
|
||||
|
||||
if(mc != MC_MATROSKA && mc != MC_WEBM)
|
||||
if(m_cfg->m_type != MC_MATROSKA && m_cfg->m_type != MC_WEBM)
|
||||
return NULL;
|
||||
|
||||
tm = calloc(1, sizeof(tvh_muxer_t));
|
||||
|
@ -241,8 +241,7 @@ tvh_muxer_create(muxer_container_type_t mc, const muxer_config_t *m_cfg)
|
|||
tm->m_write_pkt = tvh_muxer_write_pkt;
|
||||
tm->m_close = tvh_muxer_close;
|
||||
tm->m_destroy = tvh_muxer_destroy;
|
||||
tm->m_container = mc;
|
||||
tm->tm_ref = mk_mux_create((muxer_t *)tm, mc == MC_WEBM);
|
||||
tm->tm_ref = mk_mux_create((muxer_t *)tm, m_cfg->m_type == MC_WEBM);
|
||||
|
||||
return (muxer_t*)tm;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
#include "muxer.h"
|
||||
|
||||
muxer_t* tvh_muxer_create
|
||||
(muxer_container_type_t mc, const muxer_config_t* m_cfg);
|
||||
muxer_t* tvh_muxer_create (const muxer_config_t* m_cfg);
|
||||
|
||||
#endif
|
||||
|
|
541
src/profile.c
Normal file
541
src/profile.c
Normal file
|
@ -0,0 +1,541 @@
|
|||
/*
|
||||
* tvheadend, Stream Profile
|
||||
* 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 "settings.h"
|
||||
#include "profile.h"
|
||||
#include "streaming.h"
|
||||
#include "plumbing/tsfix.h"
|
||||
#include "plumbing/globalheaders.h"
|
||||
#include "dvr/dvr.h"
|
||||
|
||||
profile_builders_queue profile_builders;
|
||||
|
||||
struct profile_entry_queue profiles;
|
||||
|
||||
static profile_t *profile_default;
|
||||
|
||||
static void profile_class_save ( idnode_t *in );
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
void
|
||||
profile_register(const idclass_t *clazz, profile_builder_t builder)
|
||||
{
|
||||
profile_build_t *pb = calloc(1, sizeof(*pb));
|
||||
pb->clazz = clazz;
|
||||
pb->build = builder;
|
||||
LIST_INSERT_HEAD(&profile_builders, pb, link);
|
||||
}
|
||||
|
||||
static profile_build_t *
|
||||
profile_class_find(const char *name)
|
||||
{
|
||||
profile_build_t *pb;
|
||||
LIST_FOREACH(pb, &profile_builders, link) {
|
||||
if (strcmp(pb->clazz->ic_class, name) == 0)
|
||||
return pb;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
profile_t *
|
||||
profile_create
|
||||
(const char *uuid, htsmsg_t *conf, int save)
|
||||
{
|
||||
profile_t *pro = NULL;
|
||||
profile_build_t *pb = NULL;
|
||||
const char *s;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if ((s = htsmsg_get_str(conf, "class")) != NULL)
|
||||
pb = profile_class_find(s);
|
||||
if (pb == NULL) {
|
||||
tvherror("profile", "wrong class %s!", s);
|
||||
abort();
|
||||
}
|
||||
pro = pb->build();
|
||||
if (pro == NULL) {
|
||||
tvherror("profile", "Profile class %s is not available!", s);
|
||||
return NULL;
|
||||
}
|
||||
LIST_INIT(&pro->pro_dvr_configs);
|
||||
if (idnode_insert(&pro->pro_id, uuid, pb->clazz, 0)) {
|
||||
if (uuid)
|
||||
tvherror("profile", "invalid uuid '%s'", uuid);
|
||||
free(pro);
|
||||
return NULL;
|
||||
}
|
||||
if (conf) {
|
||||
int b;
|
||||
idnode_load(&pro->pro_id, conf);
|
||||
if (!htsmsg_get_bool(conf, "shield", &b))
|
||||
pro->pro_shield = !!b;
|
||||
}
|
||||
TAILQ_INSERT_TAIL(&profiles, pro, pro_link);
|
||||
if (save)
|
||||
profile_class_save((idnode_t *)pro);
|
||||
if (pro->pro_conf_changed)
|
||||
pro->pro_conf_changed(pro);
|
||||
return pro;
|
||||
}
|
||||
|
||||
static void
|
||||
profile_delete(profile_t *pro, int delconf)
|
||||
{
|
||||
pro->pro_enabled = 0;
|
||||
if (pro->pro_conf_changed)
|
||||
pro->pro_conf_changed(pro);
|
||||
if (delconf)
|
||||
hts_settings_remove("profile/%s", idnode_uuid_as_str(&pro->pro_id));
|
||||
TAILQ_REMOVE(&profiles, pro, pro_link);
|
||||
idnode_unlink(&pro->pro_id);
|
||||
dvr_config_destroy_by_profile(pro, delconf);
|
||||
if (pro->pro_free)
|
||||
pro->pro_free(pro);
|
||||
free(pro->pro_name);
|
||||
free(pro->pro_comment);
|
||||
free(pro);
|
||||
}
|
||||
|
||||
static void
|
||||
profile_class_save ( idnode_t *in )
|
||||
{
|
||||
profile_t *pro = (profile_t *)in;
|
||||
htsmsg_t *c = htsmsg_create_map();
|
||||
idnode_save(in, c);
|
||||
if (pro->pro_shield)
|
||||
htsmsg_add_bool(c, "shield", 1);
|
||||
hts_settings_save(c, "profile/%s", idnode_uuid_as_str(in));
|
||||
htsmsg_destroy(c);
|
||||
if (pro->pro_conf_changed)
|
||||
pro->pro_conf_changed(pro);
|
||||
}
|
||||
|
||||
static const char *
|
||||
profile_class_get_title ( idnode_t *in )
|
||||
{
|
||||
profile_t *pro = (profile_t *)in;
|
||||
static char buf[32];
|
||||
if (pro->pro_name && pro->pro_name[0])
|
||||
return pro->pro_name;
|
||||
snprintf(buf, sizeof(buf), "%s", in->in_class->ic_caption);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void
|
||||
profile_class_delete(idnode_t *self)
|
||||
{
|
||||
profile_t *pro = (profile_t *)self;
|
||||
if (pro->pro_shield)
|
||||
return;
|
||||
profile_delete(pro, 1);
|
||||
}
|
||||
|
||||
static const void *
|
||||
profile_class_class_get(void *o)
|
||||
{
|
||||
profile_t *pro = o;
|
||||
static const char *ret;
|
||||
ret = pro->pro_id.in_class->ic_class;
|
||||
return &ret;
|
||||
}
|
||||
|
||||
static int
|
||||
profile_class_class_set(void *o, const void *v)
|
||||
{
|
||||
/* just ignore, create fcn does the right job */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const void *
|
||||
profile_class_default_get(void *o)
|
||||
{
|
||||
static int res;
|
||||
res = o == profile_default;
|
||||
return &res;
|
||||
}
|
||||
|
||||
static int
|
||||
profile_class_default_set(void *o, const void *v)
|
||||
{
|
||||
profile_t *pro = o, *old;
|
||||
if (*(int *)v && pro != profile_default) {
|
||||
old = profile_default;
|
||||
profile_default = pro;
|
||||
if (old)
|
||||
profile_class_save(&old->pro_id);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const idclass_t profile_class =
|
||||
{
|
||||
.ic_class = "profile",
|
||||
.ic_caption = "Stream Profile",
|
||||
.ic_save = profile_class_save,
|
||||
.ic_event = "profile",
|
||||
.ic_get_title = profile_class_get_title,
|
||||
.ic_delete = profile_class_delete,
|
||||
.ic_properties = (const property_t[]){
|
||||
{
|
||||
.type = PT_STR,
|
||||
.id = "class",
|
||||
.name = "Class",
|
||||
.opts = PO_RDONLY | PO_HIDDEN,
|
||||
.get = profile_class_class_get,
|
||||
.set = profile_class_class_set,
|
||||
},
|
||||
{
|
||||
.type = PT_BOOL,
|
||||
.id = "enabled",
|
||||
.name = "Enabled",
|
||||
.off = offsetof(profile_t, pro_enabled),
|
||||
},
|
||||
{
|
||||
.type = PT_BOOL,
|
||||
.id = "default",
|
||||
.name = "Default",
|
||||
.set = profile_class_default_set,
|
||||
.get = profile_class_default_get,
|
||||
},
|
||||
{
|
||||
.type = PT_STR,
|
||||
.id = "name",
|
||||
.name = "Profile Name",
|
||||
.off = offsetof(profile_t, pro_name),
|
||||
.notify = idnode_notify_title_changed,
|
||||
},
|
||||
{
|
||||
.type = PT_STR,
|
||||
.id = "comment",
|
||||
.name = "Comment",
|
||||
.off = offsetof(profile_t, pro_comment),
|
||||
},
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
const char *
|
||||
profile_get_name(profile_t *pro)
|
||||
{
|
||||
if (pro->pro_name && *pro->pro_name) return pro->pro_name;
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
profile_t *
|
||||
profile_find_by_name(const char *name)
|
||||
{
|
||||
profile_t *pro;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if (!name)
|
||||
return profile_default;
|
||||
|
||||
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
||||
if (!strcmp(pro->pro_name, name))
|
||||
return pro;
|
||||
}
|
||||
|
||||
return profile_default;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
char *
|
||||
profile_validate_name(const char *name)
|
||||
{
|
||||
profile_t *pro;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
TAILQ_FOREACH(pro, &profiles, pro_link) {
|
||||
if (name && !strcmp(pro->pro_name, name))
|
||||
return strdup(name);
|
||||
}
|
||||
|
||||
if (profile_default)
|
||||
return strdup(profile_default->pro_name);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
htsmsg_t *
|
||||
profile_class_get_list(void *o)
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
htsmsg_add_str(m, "type", "api");
|
||||
htsmsg_add_str(m, "uri", "profile/list");
|
||||
htsmsg_add_str(m, "event", "profile");
|
||||
return m;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
int
|
||||
profile_chain_raw_open(profile_chain_t *prch, size_t qsize)
|
||||
{
|
||||
muxer_config_t c;
|
||||
|
||||
memset(&c, 0, sizeof(c));
|
||||
c.m_type = MC_RAW;
|
||||
memset(prch, 0, sizeof(*prch));
|
||||
prch->prch_flags = SUBSCRIPTION_RAW_MPEGTS;
|
||||
streaming_queue_init(&prch->prch_sq, SMT_PACKET, qsize);
|
||||
prch->prch_st = &prch->prch_sq.sq_st;
|
||||
prch->prch_muxer = muxer_create(&c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
void
|
||||
profile_chain_close(profile_chain_t *prch)
|
||||
{
|
||||
if (prch->prch_tsfix)
|
||||
tsfix_destroy(prch->prch_tsfix);
|
||||
if (prch->prch_gh)
|
||||
globalheaders_destroy(prch->prch_gh);
|
||||
if (prch->prch_muxer)
|
||||
muxer_destroy(prch->prch_muxer);
|
||||
streaming_queue_deinit(&prch->prch_sq);
|
||||
}
|
||||
|
||||
/*
|
||||
* MPEG-TS passthrough muxer
|
||||
*/
|
||||
typedef struct profile_mpegts {
|
||||
profile_t;
|
||||
int pro_rewrite_pmt;
|
||||
int pro_rewrite_pat;
|
||||
} profile_mpegts_t;
|
||||
|
||||
const idclass_t profile_mpegts_pass_class =
|
||||
{
|
||||
.ic_super = &profile_class,
|
||||
.ic_class = "profile-mpegts",
|
||||
.ic_caption = "MPEG-TS Pass-through",
|
||||
.ic_properties = (const property_t[]){
|
||||
{
|
||||
.type = PT_BOOL,
|
||||
.id = "rewrite_pmt",
|
||||
.name = "Rewrite PMT",
|
||||
.off = offsetof(profile_mpegts_t, pro_rewrite_pmt),
|
||||
.def.i = 1,
|
||||
},
|
||||
{
|
||||
.type = PT_BOOL,
|
||||
.id = "rewrite_pat",
|
||||
.name = "Rewrite PAT",
|
||||
.off = offsetof(profile_mpegts_t, pro_rewrite_pat),
|
||||
.def.i = 1,
|
||||
},
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
static int
|
||||
profile_mpegts_pass_open(profile_t *_pro, profile_chain_t *prch,
|
||||
muxer_config_t *m_cfg, size_t qsize)
|
||||
{
|
||||
profile_mpegts_t *pro = (profile_mpegts_t *)_pro;
|
||||
muxer_config_t c;
|
||||
|
||||
if (m_cfg)
|
||||
c = *m_cfg; /* do not alter the original parameter */
|
||||
else
|
||||
memset(&c, 0, sizeof(c));
|
||||
if (c.m_type != MC_RAW)
|
||||
c.m_type = MC_PASS;
|
||||
c.m_rewrite_pat = pro->pro_rewrite_pat;
|
||||
c.m_rewrite_pmt = pro->pro_rewrite_pmt;
|
||||
|
||||
memset(prch, 0, sizeof(*prch));
|
||||
prch->prch_flags = SUBSCRIPTION_RAW_MPEGTS;
|
||||
streaming_queue_init(&prch->prch_sq, SMT_PACKET, qsize);
|
||||
prch->prch_muxer = muxer_create(&c);
|
||||
prch->prch_st = &prch->prch_sq.sq_st;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static muxer_container_type_t
|
||||
profile_mpegts_pass_get_mc(profile_t *_pro)
|
||||
{
|
||||
return MC_PASS;
|
||||
}
|
||||
|
||||
static profile_t *
|
||||
profile_mpegts_pass_builder(void)
|
||||
{
|
||||
profile_mpegts_t *pro = calloc(1, sizeof(*pro));
|
||||
pro->pro_open = profile_mpegts_pass_open;
|
||||
pro->pro_get_mc = profile_mpegts_pass_get_mc;
|
||||
return (profile_t *)pro;
|
||||
}
|
||||
|
||||
/*
|
||||
* Matroska muxer
|
||||
*/
|
||||
typedef struct profile_matroska {
|
||||
profile_t;
|
||||
int pro_webm;
|
||||
} profile_matroska_t;
|
||||
|
||||
const idclass_t profile_matroska_class =
|
||||
{
|
||||
.ic_super = &profile_class,
|
||||
.ic_class = "profile-matroska",
|
||||
.ic_caption = "Matroska (mkv)",
|
||||
.ic_properties = (const property_t[]){
|
||||
{
|
||||
.type = PT_BOOL,
|
||||
.id = "webm",
|
||||
.name = "WEBM",
|
||||
.off = offsetof(profile_matroska_t, pro_webm),
|
||||
.def.i = 0,
|
||||
},
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
static int
|
||||
profile_matroska_open(profile_t *_pro, profile_chain_t *prch,
|
||||
muxer_config_t *m_cfg, size_t qsize)
|
||||
{
|
||||
profile_matroska_t *pro = (profile_matroska_t *)_pro;
|
||||
muxer_config_t c;
|
||||
|
||||
if (m_cfg)
|
||||
c = *m_cfg; /* do not alter the original parameter */
|
||||
else
|
||||
memset(&c, 0, sizeof(c));
|
||||
if (c.m_type != MC_WEBM)
|
||||
c.m_type = MC_MATROSKA;
|
||||
if (pro->pro_webm)
|
||||
c.m_type = MC_WEBM;
|
||||
|
||||
memset(prch, 0, sizeof(*prch));
|
||||
streaming_queue_init(&prch->prch_sq, 0, qsize);
|
||||
prch->prch_gh = globalheaders_create(&prch->prch_sq.sq_st);
|
||||
prch->prch_tsfix = tsfix_create(prch->prch_gh);
|
||||
prch->prch_muxer = muxer_create(&c);
|
||||
prch->prch_st = prch->prch_tsfix;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static muxer_container_type_t
|
||||
profile_matroska_get_mc(profile_t *_pro)
|
||||
{
|
||||
profile_matroska_t *pro = (profile_matroska_t *)_pro;
|
||||
if (pro->pro_webm)
|
||||
return MC_WEBM;
|
||||
return MC_MATROSKA;
|
||||
}
|
||||
|
||||
static profile_t *
|
||||
profile_matroska_builder(void)
|
||||
{
|
||||
profile_matroska_t *pro = calloc(1, sizeof(*pro));
|
||||
pro->pro_open = profile_matroska_open;
|
||||
pro->pro_get_mc = profile_matroska_get_mc;
|
||||
return (profile_t *)pro;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize
|
||||
*/
|
||||
void
|
||||
profile_init(void)
|
||||
{
|
||||
htsmsg_t *c, *e;
|
||||
htsmsg_field_t *f;
|
||||
|
||||
LIST_INIT(&profile_builders);
|
||||
TAILQ_INIT(&profiles);
|
||||
|
||||
profile_register(&profile_mpegts_pass_class, profile_mpegts_pass_builder);
|
||||
profile_register(&profile_matroska_class, profile_matroska_builder);
|
||||
|
||||
if ((c = hts_settings_load("profile")) != NULL) {
|
||||
HTSMSG_FOREACH(f, c) {
|
||||
if (!(e = htsmsg_field_get_map(f)))
|
||||
continue;
|
||||
(void)profile_create(f->hmf_name, e, 0);
|
||||
}
|
||||
htsmsg_destroy(c);
|
||||
}
|
||||
|
||||
if (TAILQ_EMPTY(&profiles)) {
|
||||
htsmsg_t *conf;
|
||||
|
||||
conf = htsmsg_create_map();
|
||||
htsmsg_add_str (conf, "class", "profile-mpegts");
|
||||
htsmsg_add_bool(conf, "enabled", 1);
|
||||
htsmsg_add_bool(conf, "default", 1);
|
||||
htsmsg_add_str (conf, "name", "pass");
|
||||
htsmsg_add_str (conf, "comment", "MPEG-TS Pass-through");
|
||||
htsmsg_add_bool(conf, "rewrite_pmt", 1);
|
||||
htsmsg_add_bool(conf, "rewrite_pat", 1);
|
||||
htsmsg_add_bool(conf, "shield", 1);
|
||||
(void)profile_create(NULL, conf, 1);
|
||||
htsmsg_destroy(conf);
|
||||
|
||||
conf = htsmsg_create_map();
|
||||
htsmsg_add_str (conf, "class", "profile-matroska");
|
||||
htsmsg_add_bool(conf, "enabled", 1);
|
||||
htsmsg_add_str (conf, "name", "matroska");
|
||||
htsmsg_add_str (conf, "comment", "Matroska");
|
||||
htsmsg_add_bool(conf, "shield", 1);
|
||||
(void)profile_create(NULL, conf, 1);
|
||||
htsmsg_destroy(conf);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
profile_done(void)
|
||||
{
|
||||
profile_t *pro;
|
||||
profile_build_t *pb;
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
profile_default = NULL;
|
||||
while ((pro = TAILQ_FIRST(&profiles)) != NULL)
|
||||
profile_delete(pro, 0);
|
||||
while ((pb = LIST_FIRST(&profile_builders)) != NULL) {
|
||||
LIST_REMOVE(pb, link);
|
||||
free(pb);
|
||||
}
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
}
|
102
src/profile.h
Normal file
102
src/profile.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* tvheadend, Stream Profile
|
||||
* 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_PROFILE_H__
|
||||
#define __TVH_PROFILE_H__
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "idnode.h"
|
||||
#include "muxer.h"
|
||||
|
||||
struct profile;
|
||||
struct muxer;
|
||||
struct streaming_target;
|
||||
|
||||
extern const idclass_t profile_class;
|
||||
extern const idclass_t profile_mpegts_pass_class;
|
||||
extern const idclass_t profile_matroska_class;
|
||||
|
||||
TAILQ_HEAD(profile_entry_queue, profile);
|
||||
|
||||
extern struct profile_entry_queue profiles;
|
||||
|
||||
typedef struct profile *(*profile_builder_t)(void);
|
||||
|
||||
typedef struct profile_build {
|
||||
LIST_ENTRY(profile_build) link;
|
||||
const idclass_t *clazz;
|
||||
profile_builder_t build;
|
||||
} profile_build_t;
|
||||
|
||||
typedef LIST_HEAD(, profile_build) profile_builders_queue;
|
||||
|
||||
extern profile_builders_queue profile_builders;
|
||||
|
||||
typedef struct profile_chain {
|
||||
int prch_flags;
|
||||
struct streaming_queue prch_sq;
|
||||
struct streaming_target *prch_st;
|
||||
struct muxer *prch_muxer;
|
||||
struct streaming_target *prch_gh;
|
||||
struct streaming_target *prch_tsfix;
|
||||
} profile_chain_t;
|
||||
|
||||
typedef struct profile {
|
||||
idnode_t pro_id;
|
||||
TAILQ_ENTRY(profile) pro_link;
|
||||
|
||||
LIST_HEAD(,dvr_config) pro_dvr_configs;
|
||||
|
||||
int pro_enabled;
|
||||
int pro_shield;
|
||||
char *pro_name;
|
||||
char *pro_comment;
|
||||
|
||||
void (*pro_free)(struct profile *cac);
|
||||
void (*pro_conf_changed)(struct profile *cac);
|
||||
muxer_container_type_t (*pro_get_mc)(struct profile *cac);
|
||||
|
||||
int (*pro_open)(struct profile *cac, profile_chain_t *prch,
|
||||
muxer_config_t *m_cfg, size_t qsize);
|
||||
} profile_t;
|
||||
|
||||
void profile_register(const idclass_t *clazz, profile_builder_t builder);
|
||||
|
||||
profile_t *profile_create
|
||||
(const char *uuid, htsmsg_t *conf, int save);
|
||||
|
||||
int profile_chain_raw_open(profile_chain_t *prch, size_t qsize);
|
||||
void profile_chain_close(profile_chain_t *prch);
|
||||
|
||||
static inline profile_t *profile_find_by_uuid(const char *uuid)
|
||||
{ return (profile_t*)idnode_find(uuid, &profile_class, NULL); }
|
||||
profile_t *profile_find_by_name(const char *name);
|
||||
|
||||
htsmsg_t * profile_class_get_list(void *o);
|
||||
|
||||
char *profile_validate_name(const char *name);
|
||||
|
||||
const char *profile_get_name(profile_t *pro);
|
||||
|
||||
static inline muxer_container_type_t profile_get_mc(profile_t *pro)
|
||||
{ return pro->pro_get_mc(pro); }
|
||||
|
||||
void profile_init(void);
|
||||
void profile_done(void);
|
||||
|
||||
#endif /* __TVH_PROFILE_H__ */
|
|
@ -334,7 +334,7 @@ service_mapper_thread ( void *aux )
|
|||
streaming_message_t *sm;
|
||||
const char *err = NULL;
|
||||
|
||||
streaming_queue_init(&sq, 0);
|
||||
streaming_queue_init(&sq, 0, 0);
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ streaming_queue_deliver(void *opauqe, streaming_message_t *sm)
|
|||
*
|
||||
*/
|
||||
void
|
||||
streaming_queue_init2(streaming_queue_t *sq, int reject_filter, size_t maxsize)
|
||||
streaming_queue_init(streaming_queue_t *sq, int reject_filter, size_t maxsize)
|
||||
{
|
||||
streaming_target_init(&sq->sq_st, streaming_queue_deliver, sq, reject_filter);
|
||||
|
||||
|
@ -86,16 +86,6 @@ streaming_queue_init2(streaming_queue_t *sq, int reject_filter, size_t maxsize)
|
|||
sq->sq_maxsize = maxsize;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
streaming_queue_init(streaming_queue_t *sq, int reject_filter)
|
||||
{
|
||||
streaming_queue_init2(sq, reject_filter, 0); // 0 = unlimited
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -72,9 +72,7 @@ void streaming_target_init(streaming_target_t *st,
|
|||
st_callback_t *cb, void *opaque,
|
||||
int reject_filter);
|
||||
|
||||
void streaming_queue_init(streaming_queue_t *sq, int reject_filter);
|
||||
|
||||
void streaming_queue_init2
|
||||
void streaming_queue_init
|
||||
(streaming_queue_t *sq, int reject_filter, size_t maxsize);
|
||||
|
||||
void streaming_queue_clear(struct streaming_message_queue *q);
|
||||
|
|
|
@ -278,7 +278,7 @@ streaming_target_t *timeshift_create
|
|||
tvh_pipe(O_NONBLOCK, &ts->rd_pipe);
|
||||
|
||||
/* Initialise input */
|
||||
streaming_queue_init(&ts->wr_queue, 0);
|
||||
streaming_queue_init(&ts->wr_queue, 0, 0);
|
||||
streaming_target_init(&ts->input, timeshift_input, ts, 0);
|
||||
tvhthread_create(&ts->wr_thread, NULL, timeshift_writer, ts);
|
||||
tvhthread_create(&ts->rd_thread, NULL, timeshift_reader, ts);
|
||||
|
|
|
@ -1,14 +1,47 @@
|
|||
/*
|
||||
* Elementary Stream Filters
|
||||
* Stream Profiles, Elementary Stream Filters
|
||||
*/
|
||||
|
||||
tvheadend.caclient_builders = new Ext.data.JsonStore({
|
||||
url: 'api/profile/builders',
|
||||
root: 'entries',
|
||||
fields: ['class', 'caption', 'props'],
|
||||
id: 'class',
|
||||
autoLoad: true
|
||||
});
|
||||
|
||||
tvheadend.esfilter_tab = function(panel)
|
||||
{
|
||||
tvheadend.idnode_form_grid(panel, {
|
||||
url: 'api/profile',
|
||||
clazz: 'profile',
|
||||
comet: 'profile',
|
||||
titleS: 'Stream Profile',
|
||||
titleC: 'Stream Profile Name',
|
||||
iconCls: 'stream_profile',
|
||||
add: {
|
||||
url: 'api/profile',
|
||||
titleS: 'Stream Profile',
|
||||
select: {
|
||||
label: 'Type',
|
||||
store: tvheadend.caclient_builders,
|
||||
displayField: 'caption',
|
||||
valueField: 'class',
|
||||
propField: 'props'
|
||||
},
|
||||
create: { },
|
||||
},
|
||||
del: true,
|
||||
help: function() {
|
||||
new tvheadend.help('Stream Profile', 'config_profile.html');
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.idnode_grid(panel, {
|
||||
url: 'api/esfilter/video',
|
||||
titleS: 'Video Stream Filter',
|
||||
titleP: 'Video Stream Filters',
|
||||
tabIndex: 0,
|
||||
tabIndex: 1,
|
||||
add: {
|
||||
url: 'api/esfilter/video',
|
||||
create: {}
|
||||
|
@ -24,7 +57,7 @@ tvheadend.esfilter_tab = function(panel)
|
|||
url: 'api/esfilter/audio',
|
||||
titleS: 'Audio Stream Filter',
|
||||
titleP: 'Audio Stream Filters',
|
||||
tabIndex: 1,
|
||||
tabIndex: 2,
|
||||
add: {
|
||||
url: 'api/esfilter/audio',
|
||||
create: {}
|
||||
|
@ -40,7 +73,7 @@ tvheadend.esfilter_tab = function(panel)
|
|||
url: 'api/esfilter/teletext',
|
||||
titleS: 'Teletext Stream Filter',
|
||||
titleP: 'Teletext Stream Filters',
|
||||
tabIndex: 2,
|
||||
tabIndex: 3,
|
||||
add: {
|
||||
url: 'api/esfilter/teletext',
|
||||
create: {}
|
||||
|
@ -56,7 +89,7 @@ tvheadend.esfilter_tab = function(panel)
|
|||
url: 'api/esfilter/subtit',
|
||||
titleS: 'Subtitle Stream Filter',
|
||||
titleP: 'Subtitle Stream Filters',
|
||||
tabIndex: 3,
|
||||
tabIndex: 4,
|
||||
add: {
|
||||
url: 'api/esfilter/subtit',
|
||||
create: {}
|
||||
|
@ -72,7 +105,7 @@ tvheadend.esfilter_tab = function(panel)
|
|||
url: 'api/esfilter/ca',
|
||||
titleS: 'CA Stream Filter',
|
||||
titleP: 'CA Stream Filters',
|
||||
tabIndex: 4,
|
||||
tabIndex: 5,
|
||||
add: {
|
||||
url: 'api/esfilter/ca',
|
||||
create: {}
|
||||
|
@ -88,7 +121,7 @@ tvheadend.esfilter_tab = function(panel)
|
|||
url: 'api/esfilter/other',
|
||||
titleS: 'Other Stream Filter',
|
||||
titleP: 'Other Stream Filters',
|
||||
tabIndex: 5,
|
||||
tabIndex: 6,
|
||||
add: {
|
||||
url: 'api/esfilter/other',
|
||||
create: {}
|
||||
|
|
|
@ -378,6 +378,18 @@ function accessUpdate(o) {
|
|||
|
||||
cp.add(chepg);
|
||||
|
||||
/* Stream Config */
|
||||
var stream = new Ext.TabPanel({
|
||||
activeTab: 0,
|
||||
autoScroll: true,
|
||||
title: 'Stream',
|
||||
iconCls: 'stream_config',
|
||||
items: []
|
||||
});
|
||||
tvheadend.esfilter_tab(stream);
|
||||
|
||||
cp.add(stream);
|
||||
|
||||
/* DVR / Timeshift */
|
||||
var tsdvr = new Ext.TabPanel({
|
||||
activeTab: 0,
|
||||
|
@ -396,18 +408,6 @@ function accessUpdate(o) {
|
|||
if (tvheadend.capabilities.indexOf('caclient') !== -1)
|
||||
tvheadend.caclient(cp, null);
|
||||
|
||||
/* Stream Config */
|
||||
var stream = new Ext.TabPanel({
|
||||
activeTab: 0,
|
||||
autoScroll: true,
|
||||
title: 'Stream',
|
||||
iconCls: 'stream_config',
|
||||
items: []
|
||||
});
|
||||
tvheadend.esfilter_tab(stream);
|
||||
|
||||
cp.add(stream);
|
||||
|
||||
/* Debug */
|
||||
tvheadend.tvhlog(cp);
|
||||
|
||||
|
|
|
@ -37,15 +37,17 @@
|
|||
#include "dvr/dvr.h"
|
||||
#include "filebundle.h"
|
||||
#include "streaming.h"
|
||||
#include "plumbing/tsfix.h"
|
||||
#include "plumbing/globalheaders.h"
|
||||
#include "plumbing/transcoding.h"
|
||||
#include "profile.h"
|
||||
#include "epg.h"
|
||||
#include "muxer.h"
|
||||
#include "imagecache.h"
|
||||
#include "tcp.h"
|
||||
#include "config.h"
|
||||
#include "atomic.h"
|
||||
#if ENABLE_MPEGTS
|
||||
#include "input.h"
|
||||
#endif
|
||||
|
||||
#if defined(PLATFORM_LINUX)
|
||||
#include <sys/sendfile.h>
|
||||
|
@ -287,21 +289,20 @@ http_stream_postop ( void *tcp_id )
|
|||
* HTTP stream loop
|
||||
*/
|
||||
static void
|
||||
http_stream_run(http_connection_t *hc, streaming_queue_t *sq,
|
||||
const char *name, muxer_container_type_t mc,
|
||||
th_subscription_t *s, muxer_config_t *mcfg)
|
||||
http_stream_run(http_connection_t *hc, profile_chain_t *prch,
|
||||
const char *name, th_subscription_t *s)
|
||||
{
|
||||
streaming_message_t *sm;
|
||||
int run = 1;
|
||||
int started = 0;
|
||||
muxer_t *mux = NULL;
|
||||
streaming_queue_t *sq = &prch->prch_sq;
|
||||
muxer_t *mux = prch->prch_muxer;
|
||||
int timeouts = 0, grace = 20;
|
||||
struct timespec ts;
|
||||
struct timeval tp;
|
||||
int err = 0;
|
||||
socklen_t errlen = sizeof(err);
|
||||
|
||||
mux = muxer_create(mc, mcfg);
|
||||
if(muxer_open_stream(mux, hc->hc_fd))
|
||||
run = 0;
|
||||
|
||||
|
@ -419,8 +420,6 @@ http_stream_run(http_connection_t *hc, streaming_queue_t *sq,
|
|||
|
||||
if(started)
|
||||
muxer_close(mux);
|
||||
|
||||
muxer_destroy(mux);
|
||||
}
|
||||
|
||||
|
||||
|
@ -433,14 +432,12 @@ http_channel_playlist(http_connection_t *hc, channel_t *channel)
|
|||
htsbuf_queue_t *hq;
|
||||
char buf[255];
|
||||
const char *host;
|
||||
muxer_container_type_t mc;
|
||||
char *profile;
|
||||
|
||||
if (http_access_verify_channel(hc, ACCESS_STREAMING, channel, 1))
|
||||
return HTTP_STATUS_UNAUTHORIZED;
|
||||
|
||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
||||
if(mc == MC_UNKNOWN)
|
||||
mc = dvr_config_find_by_name_default(NULL)->dvr_mc;
|
||||
profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
|
||||
|
||||
hq = &hc->hc_reply;
|
||||
host = http_arg_get(&hc->hc_args, "Host");
|
||||
|
@ -472,10 +469,11 @@ http_channel_playlist(http_connection_t *hc, channel_t *channel)
|
|||
htsbuf_qprintf(hq, "&scodec=%s", streaming_component_type2txt(props.tp_scodec));
|
||||
}
|
||||
#endif
|
||||
htsbuf_qprintf(hq, "&mux=%s\n", muxer_container_type2txt(mc));
|
||||
htsbuf_qprintf(hq, "&profile=%s\n", profile);
|
||||
|
||||
http_output_content(hc, "audio/x-mpegurl");
|
||||
|
||||
free(profile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -490,14 +488,12 @@ http_tag_playlist(http_connection_t *hc, channel_tag_t *tag)
|
|||
char buf[255];
|
||||
channel_tag_mapping_t *ctm;
|
||||
const char *host;
|
||||
muxer_container_type_t mc;
|
||||
char *profile;
|
||||
|
||||
hq = &hc->hc_reply;
|
||||
host = http_arg_get(&hc->hc_args, "Host");
|
||||
|
||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
||||
if(mc == MC_UNKNOWN)
|
||||
mc = dvr_config_find_by_name_default(NULL)->dvr_mc;
|
||||
profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
|
||||
|
||||
htsbuf_qprintf(hq, "#EXTM3U\n");
|
||||
LIST_FOREACH(ctm, &tag->ct_ctms, ctm_tag_link) {
|
||||
|
@ -507,11 +503,12 @@ http_tag_playlist(http_connection_t *hc, channel_tag_t *tag)
|
|||
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel_get_name(ctm->ctm_channel));
|
||||
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
||||
access_ticket_create(buf, hc->hc_access));
|
||||
htsbuf_qprintf(hq, "&mux=%s\n", muxer_container_type2txt(mc));
|
||||
htsbuf_qprintf(hq, "&profile=%s\n", profile);
|
||||
}
|
||||
|
||||
http_output_content(hc, "audio/x-mpegurl");
|
||||
|
||||
free(profile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -526,14 +523,12 @@ http_tag_list_playlist(http_connection_t *hc)
|
|||
char buf[255];
|
||||
channel_tag_t *ct;
|
||||
const char *host;
|
||||
muxer_container_type_t mc;
|
||||
char *profile;
|
||||
|
||||
hq = &hc->hc_reply;
|
||||
host = http_arg_get(&hc->hc_args, "Host");
|
||||
|
||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
||||
if(mc == MC_UNKNOWN)
|
||||
mc = dvr_config_find_by_name_default(NULL)->dvr_mc;
|
||||
profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
|
||||
|
||||
htsbuf_qprintf(hq, "#EXTM3U\n");
|
||||
TAILQ_FOREACH(ct, &channel_tags, ct_link) {
|
||||
|
@ -544,11 +539,12 @@ http_tag_list_playlist(http_connection_t *hc)
|
|||
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ct->ct_name);
|
||||
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
||||
access_ticket_create(buf, hc->hc_access));
|
||||
htsbuf_qprintf(hq, "&mux=%s\n", muxer_container_type2txt(mc));
|
||||
htsbuf_qprintf(hq, "&profile=%s\n", profile);
|
||||
}
|
||||
|
||||
http_output_content(hc, "audio/x-mpegurl");
|
||||
|
||||
free(profile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -575,14 +571,12 @@ http_channel_list_playlist(http_connection_t *hc)
|
|||
channel_t **chlist;
|
||||
const char *host;
|
||||
int idx = 0, count = 0;
|
||||
muxer_container_type_t mc;
|
||||
char *profile;
|
||||
|
||||
hq = &hc->hc_reply;
|
||||
host = http_arg_get(&hc->hc_args, "Host");
|
||||
|
||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
||||
if(mc == MC_UNKNOWN)
|
||||
mc = dvr_config_find_by_name_default(NULL)->dvr_mc;
|
||||
profile = profile_validate_name(http_arg_get(&hc->hc_req_args, "profile"));
|
||||
|
||||
CHANNEL_FOREACH(ch)
|
||||
count++;
|
||||
|
@ -608,13 +602,14 @@ http_channel_list_playlist(http_connection_t *hc)
|
|||
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel_get_name(ch));
|
||||
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
|
||||
access_ticket_create(buf, hc->hc_access));
|
||||
htsbuf_qprintf(hq, "&mux=%s\n", muxer_container_type2txt(mc));
|
||||
htsbuf_qprintf(hq, "&profile=%s\n", profile);
|
||||
}
|
||||
|
||||
free(chlist);
|
||||
|
||||
http_output_content(hc, "audio/x-mpegurl");
|
||||
|
||||
free(profile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -789,77 +784,51 @@ page_http_playlist(http_connection_t *hc, const char *remain, void *opaque)
|
|||
static int
|
||||
http_stream_service(http_connection_t *hc, service_t *service, int weight)
|
||||
{
|
||||
streaming_queue_t sq;
|
||||
th_subscription_t *s;
|
||||
streaming_target_t *gh;
|
||||
streaming_target_t *tsfix;
|
||||
streaming_target_t *st;
|
||||
dvr_config_t *cfg;
|
||||
muxer_container_type_t mc;
|
||||
int flags = SUBSCRIPTION_STREAMING;
|
||||
profile_t *pro;
|
||||
profile_chain_t prch;
|
||||
const char *str;
|
||||
size_t qsize;
|
||||
const char *name;
|
||||
char addrbuf[50];
|
||||
void *tcp_id;
|
||||
int res = 0;
|
||||
int res = HTTP_STATUS_BAD_REQUEST;
|
||||
|
||||
if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
|
||||
return HTTP_STATUS_UNAUTHORIZED;
|
||||
|
||||
if((tcp_id = http_stream_preop(hc)) == NULL)
|
||||
if(!(pro = profile_find_by_name(http_arg_get(&hc->hc_req_args, "profile"))))
|
||||
return HTTP_STATUS_NOT_ALLOWED;
|
||||
|
||||
cfg = dvr_config_find_by_name_default(NULL);
|
||||
|
||||
/* Build muxer config - this takes the defaults from the default dvr config, which is a hack */
|
||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
||||
if(mc == MC_UNKNOWN) {
|
||||
mc = cfg->dvr_mc;
|
||||
}
|
||||
if((tcp_id = http_stream_preop(hc)) == NULL)
|
||||
return HTTP_STATUS_NOT_ALLOWED;
|
||||
|
||||
if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
|
||||
qsize = atoll(str);
|
||||
else
|
||||
qsize = 1500000;
|
||||
|
||||
if(mc == MC_PASS || mc == MC_RAW) {
|
||||
streaming_queue_init2(&sq, SMT_PACKET, qsize);
|
||||
gh = NULL;
|
||||
tsfix = NULL;
|
||||
st = &sq.sq_st;
|
||||
flags |= SUBSCRIPTION_RAW_MPEGTS;
|
||||
} else {
|
||||
streaming_queue_init2(&sq, 0, qsize);
|
||||
gh = globalheaders_create(&sq.sq_st);
|
||||
tsfix = tsfix_create(gh);
|
||||
st = tsfix;
|
||||
if (!pro->pro_open(pro, &prch, NULL, qsize)) {
|
||||
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||
|
||||
s = subscription_create_from_service(service, weight ?: 100, "HTTP",
|
||||
prch.prch_st,
|
||||
prch.prch_flags | SUBSCRIPTION_STREAMING,
|
||||
addrbuf,
|
||||
hc->hc_username,
|
||||
http_arg_get(&hc->hc_args, "User-Agent"));
|
||||
if(s) {
|
||||
name = tvh_strdupa(service->s_nicename);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
http_stream_run(hc, &prch, name, s);
|
||||
pthread_mutex_lock(&global_lock);
|
||||
subscription_unsubscribe(s);
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||
|
||||
s = subscription_create_from_service(service, weight ?: 100, "HTTP", st, flags,
|
||||
addrbuf,
|
||||
hc->hc_username,
|
||||
http_arg_get(&hc->hc_args, "User-Agent"));
|
||||
if(s) {
|
||||
name = tvh_strdupa(service->s_nicename);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
http_stream_run(hc, &sq, name, mc, s, &cfg->dvr_muxcnf);
|
||||
pthread_mutex_lock(&global_lock);
|
||||
subscription_unsubscribe(s);
|
||||
} else {
|
||||
res = HTTP_STATUS_BAD_REQUEST;
|
||||
}
|
||||
|
||||
if(gh)
|
||||
globalheaders_destroy(gh);
|
||||
|
||||
if(tsfix)
|
||||
tsfix_destroy(tsfix);
|
||||
|
||||
streaming_queue_deinit(&sq);
|
||||
|
||||
profile_chain_close(&prch);
|
||||
http_stream_postop(tcp_id);
|
||||
return res;
|
||||
}
|
||||
|
@ -870,17 +839,17 @@ http_stream_service(http_connection_t *hc, service_t *service, int weight)
|
|||
* TODO: can't currently force this to be on a particular input
|
||||
*/
|
||||
#if ENABLE_MPEGTS
|
||||
#include "input.h"
|
||||
static int
|
||||
http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
|
||||
{
|
||||
th_subscription_t *s;
|
||||
streaming_queue_t sq;
|
||||
profile_chain_t prch;
|
||||
size_t qsize;
|
||||
const char *name;
|
||||
char addrbuf[50];
|
||||
muxer_config_t muxcfg = { 0 };
|
||||
void *tcp_id;
|
||||
int res = 0;
|
||||
const char *str;
|
||||
int res = HTTP_STATUS_BAD_REQUEST;
|
||||
|
||||
if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
|
||||
return HTTP_STATUS_UNAUTHORIZED;
|
||||
|
@ -888,27 +857,33 @@ http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
|
|||
if((tcp_id = http_stream_preop(hc)) == NULL)
|
||||
return HTTP_STATUS_NOT_ALLOWED;
|
||||
|
||||
streaming_queue_init(&sq, SMT_PACKET);
|
||||
if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
|
||||
qsize = atoll(str);
|
||||
else
|
||||
qsize = 10000000;
|
||||
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||
s = subscription_create_from_mux(mm, weight ?: 10, "HTTP", &sq.sq_st,
|
||||
SUBSCRIPTION_RAW_MPEGTS |
|
||||
SUBSCRIPTION_FULLMUX |
|
||||
SUBSCRIPTION_STREAMING,
|
||||
addrbuf, hc->hc_username,
|
||||
http_arg_get(&hc->hc_args, "User-Agent"), NULL);
|
||||
if (s) {
|
||||
name = tvh_strdupa(s->ths_title);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
http_stream_run(hc, &sq, name, MC_RAW, s, &muxcfg);
|
||||
pthread_mutex_lock(&global_lock);
|
||||
subscription_unsubscribe(s);
|
||||
} else {
|
||||
res = HTTP_STATUS_BAD_REQUEST;
|
||||
if (!profile_chain_raw_open(&prch, qsize)) {
|
||||
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||
|
||||
s = subscription_create_from_mux(mm, weight ?: 10, "HTTP",
|
||||
prch.prch_st,
|
||||
prch.prch_flags |
|
||||
SUBSCRIPTION_FULLMUX |
|
||||
SUBSCRIPTION_STREAMING,
|
||||
addrbuf, hc->hc_username,
|
||||
http_arg_get(&hc->hc_args, "User-Agent"), NULL);
|
||||
if (s) {
|
||||
name = tvh_strdupa(s->ths_title);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
http_stream_run(hc, &prch, name, s);
|
||||
pthread_mutex_lock(&global_lock);
|
||||
subscription_unsubscribe(s);
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
streaming_queue_deinit(&sq);
|
||||
|
||||
profile_chain_close(&prch);
|
||||
http_stream_postop(tcp_id);
|
||||
|
||||
return res;
|
||||
|
@ -921,93 +896,50 @@ http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
|
|||
static int
|
||||
http_stream_channel(http_connection_t *hc, channel_t *ch, int weight)
|
||||
{
|
||||
streaming_queue_t sq;
|
||||
th_subscription_t *s;
|
||||
streaming_target_t *gh;
|
||||
streaming_target_t *tsfix;
|
||||
streaming_target_t *st;
|
||||
#if ENABLE_LIBAV
|
||||
streaming_target_t *tr = NULL;
|
||||
#endif
|
||||
dvr_config_t *cfg;
|
||||
int flags = SUBSCRIPTION_STREAMING;
|
||||
muxer_container_type_t mc;
|
||||
profile_t *pro;
|
||||
profile_chain_t prch;
|
||||
char *str;
|
||||
size_t qsize;
|
||||
const char *name;
|
||||
char addrbuf[50];
|
||||
void *tcp_id;
|
||||
int res = 0;
|
||||
int res = HTTP_STATUS_BAD_REQUEST;
|
||||
|
||||
if (http_access_verify_channel(hc, ACCESS_STREAMING, ch, 1))
|
||||
return HTTP_STATUS_UNAUTHORIZED;
|
||||
|
||||
if((tcp_id = http_stream_preop(hc)) == NULL)
|
||||
if(!(pro = profile_find_by_name(http_arg_get(&hc->hc_req_args, "profile"))))
|
||||
return HTTP_STATUS_NOT_ALLOWED;
|
||||
|
||||
cfg = dvr_config_find_by_name_default(NULL);
|
||||
|
||||
/* Build muxer config - this takes the defaults from the default dvr config, which is a hack */
|
||||
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
|
||||
if(mc == MC_UNKNOWN) {
|
||||
mc = cfg->dvr_mc;
|
||||
}
|
||||
if((tcp_id = http_stream_preop(hc)) == NULL)
|
||||
return HTTP_STATUS_NOT_ALLOWED;
|
||||
|
||||
if ((str = http_arg_get(&hc->hc_req_args, "qsize")))
|
||||
qsize = atoll(str);
|
||||
else
|
||||
qsize = 1500000;
|
||||
|
||||
if(mc == MC_PASS || mc == MC_RAW) {
|
||||
streaming_queue_init2(&sq, SMT_PACKET, qsize);
|
||||
gh = NULL;
|
||||
tsfix = NULL;
|
||||
st = &sq.sq_st;
|
||||
flags |= SUBSCRIPTION_RAW_MPEGTS;
|
||||
} else {
|
||||
streaming_queue_init2(&sq, 0, qsize);
|
||||
gh = globalheaders_create(&sq.sq_st);
|
||||
#if ENABLE_LIBAV
|
||||
transcoder_props_t props;
|
||||
if(http_get_transcoder_properties(&hc->hc_req_args, &props)) {
|
||||
tr = transcoder_create(gh);
|
||||
transcoder_set_properties(tr, &props);
|
||||
tsfix = tsfix_create(tr);
|
||||
} else
|
||||
#endif
|
||||
tsfix = tsfix_create(gh);
|
||||
st = tsfix;
|
||||
if (!pro->pro_open(pro, &prch, NULL, qsize)) {
|
||||
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||
|
||||
s = subscription_create_from_channel(ch, weight ?: 100, "HTTP",
|
||||
prch.prch_st, prch.prch_flags | SUBSCRIPTION_STREAMING,
|
||||
addrbuf, hc->hc_username,
|
||||
http_arg_get(&hc->hc_args, "User-Agent"));
|
||||
|
||||
if(s) {
|
||||
name = tvh_strdupa(channel_get_name(ch));
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
http_stream_run(hc, &prch, name, s);
|
||||
pthread_mutex_lock(&global_lock);
|
||||
subscription_unsubscribe(s);
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
|
||||
s = subscription_create_from_channel(ch, weight ?: 100, "HTTP", st, flags,
|
||||
addrbuf,
|
||||
hc->hc_username,
|
||||
http_arg_get(&hc->hc_args, "User-Agent"));
|
||||
|
||||
if(s) {
|
||||
name = tvh_strdupa(channel_get_name(ch));
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
http_stream_run(hc, &sq, name, mc, s, &cfg->dvr_muxcnf);
|
||||
pthread_mutex_lock(&global_lock);
|
||||
subscription_unsubscribe(s);
|
||||
} else {
|
||||
res = HTTP_STATUS_BAD_REQUEST;
|
||||
}
|
||||
|
||||
if(gh)
|
||||
globalheaders_destroy(gh);
|
||||
|
||||
#if ENABLE_LIBAV
|
||||
if(tr)
|
||||
transcoder_destroy(tr);
|
||||
#endif
|
||||
|
||||
if(tsfix)
|
||||
tsfix_destroy(tsfix);
|
||||
|
||||
streaming_queue_deinit(&sq);
|
||||
|
||||
profile_chain_close(&prch);
|
||||
http_stream_postop(tcp_id);
|
||||
|
||||
return res;
|
||||
|
|
Loading…
Add table
Reference in a new issue