Add stream profile support

This commit is contained in:
Jaroslav Kysela 2014-10-07 19:13:32 +02:00
parent 29d5c75126
commit 2012dc8e3d
27 changed files with 1101 additions and 316 deletions

View file

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

View file

@ -134,6 +134,7 @@ void api_init ( void )
api_access_init();
api_dvr_init();
api_caclient_init();
api_profile_init();
}
void api_done ( void )

View file

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

View file

@ -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
View 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);
}

View file

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

View file

@ -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);
}
}
/**
*
*/

View file

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

View file

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

View file

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

View file

@ -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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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: {}

View file

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

View file

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