From 2012dc8e3d0814cad51cd8f9401dd75035981f67 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 7 Oct 2014 19:13:32 +0200 Subject: [PATCH] Add stream profile support --- Makefile | 8 +- src/api.c | 1 + src/api.h | 1 + src/api/api_caclient.c | 2 + src/api/api_profile.c | 110 ++++++ src/dvr/dvr.h | 18 +- src/dvr/dvr_config.c | 101 ++++-- src/dvr/dvr_db.c | 4 +- src/dvr/dvr_rec.c | 96 +++--- src/main.c | 4 + src/muxer.c | 12 +- src/muxer.h | 7 +- src/muxer/muxer_libav.c | 25 +- src/muxer/muxer_libav.h | 3 +- src/muxer/muxer_pass.c | 4 +- src/muxer/muxer_pass.h | 3 +- src/muxer/muxer_tvh.c | 11 +- src/muxer/muxer_tvh.h | 3 +- src/profile.c | 541 ++++++++++++++++++++++++++++++ src/profile.h | 102 ++++++ src/service_mapper.c | 2 +- src/streaming.c | 12 +- src/streaming.h | 4 +- src/timeshift.c | 2 +- src/webui/static/app/esfilter.js | 47 ++- src/webui/static/app/tvheadend.js | 24 +- src/webui/webui.c | 270 ++++++--------- 27 files changed, 1101 insertions(+), 316 deletions(-) create mode 100644 src/api/api_profile.c create mode 100644 src/profile.c create mode 100644 src/profile.h diff --git a/Makefile b/Makefile index 04ba2840..830f62c2 100644 --- a/Makefile +++ b/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 \ diff --git a/src/api.c b/src/api.c index f0ac5be3..8594706a 100644 --- a/src/api.c +++ b/src/api.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 ) diff --git a/src/api.h b/src/api.h index c6fe371c..4928484d 100644 --- a/src/api.h +++ b/src/api.h @@ -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 diff --git a/src/api/api_caclient.c b/src/api/api_caclient.c index dc30cc85..505bce19 100644 --- a/src/api/api_caclient.c +++ b/src/api/api_caclient.c @@ -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; diff --git a/src/api/api_profile.c b/src/api/api_profile.c new file mode 100644 index 00000000..7d9ed663 --- /dev/null +++ b/src/api/api_profile.c @@ -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 . + */ + +#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); +} diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index eccace43..1dce3c65 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -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); + /* * */ diff --git a/src/dvr/dvr_config.c b/src/dvr/dvr_config.c index 1175dcec..6edf256e 100644 --- a/src/dvr/dvr_config.c +++ b/src/dvr/dvr_config.c @@ -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); + } +} + /** * */ diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 2764c50c..c31144f7 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -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, diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index a1113caa..3b8b914f 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -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) diff --git a/src/main.c b/src/main.c index a83ad9dc..15ea889b 100644 --- a/src/main.c +++ b/src/main.c @@ -69,6 +69,7 @@ #include "libav.h" #include "plumbing/transcoding.h" #endif +#include "profile.h" #ifdef PLATFORM_LINUX #include @@ -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); diff --git a/src/muxer.c b/src/muxer.c index 033c4ac2..d673b611 100644 --- a/src/muxer.c +++ b/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; } diff --git a/src/muxer.h b/src/muxer.h index 2c6b9651..8b7a279e 100644 --- a/src/muxer.h +++ b/src/muxer.h @@ -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); diff --git a/src/muxer/muxer_libav.c b/src/muxer/muxer_libav.c index aeaf6f58..28b7a73e 100644 --- a/src/muxer/muxer_libav.c +++ b/src/muxer/muxer_libav.c @@ -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; diff --git a/src/muxer/muxer_libav.h b/src/muxer/muxer_libav.h index e8e719bf..4c9ec609 100644 --- a/src/muxer/muxer_libav.h +++ b/src/muxer/muxer_libav.h @@ -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 diff --git a/src/muxer/muxer_pass.c b/src/muxer/muxer_pass.c index 18b767a1..4985b3a1 100644 --- a/src/muxer/muxer_pass.c +++ b/src/muxer/muxer_pass.c @@ -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)); diff --git a/src/muxer/muxer_pass.h b/src/muxer/muxer_pass.h index 637d312d..af52b801 100644 --- a/src/muxer/muxer_pass.h +++ b/src/muxer/muxer_pass.h @@ -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 diff --git a/src/muxer/muxer_tvh.c b/src/muxer/muxer_tvh.c index f621f954..02e9edc2 100644 --- a/src/muxer/muxer_tvh.c +++ b/src/muxer/muxer_tvh.c @@ -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; } diff --git a/src/muxer/muxer_tvh.h b/src/muxer/muxer_tvh.h index 488e443a..6c8b92fa 100644 --- a/src/muxer/muxer_tvh.h +++ b/src/muxer/muxer_tvh.h @@ -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 diff --git a/src/profile.c b/src/profile.c new file mode 100644 index 00000000..bc2b4728 --- /dev/null +++ b/src/profile.c @@ -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 . + */ + +#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); +} diff --git a/src/profile.h b/src/profile.h new file mode 100644 index 00000000..6232e6bf --- /dev/null +++ b/src/profile.h @@ -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 . + */ + +#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__ */ diff --git a/src/service_mapper.c b/src/service_mapper.c index d7fce5c0..30edd9e3 100644 --- a/src/service_mapper.c +++ b/src/service_mapper.c @@ -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); diff --git a/src/streaming.c b/src/streaming.c index a09a484e..2da04ed7 100644 --- a/src/streaming.c +++ b/src/streaming.c @@ -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 -} - - /** * */ diff --git a/src/streaming.h b/src/streaming.h index a5b5cdfa..8f1cde80 100644 --- a/src/streaming.h +++ b/src/streaming.h @@ -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); diff --git a/src/timeshift.c b/src/timeshift.c index c4bfa560..12e7601f 100644 --- a/src/timeshift.c +++ b/src/timeshift.c @@ -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); diff --git a/src/webui/static/app/esfilter.js b/src/webui/static/app/esfilter.js index 84e97626..974bdcd3 100644 --- a/src/webui/static/app/esfilter.js +++ b/src/webui/static/app/esfilter.js @@ -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: {} diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index c96024b7..6b5008b2 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -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); diff --git a/src/webui/webui.c b/src/webui/webui.c index 1c85657f..269eee55 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -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 @@ -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;